feat: stop captured error_output at first null byte (learning-capture spec edge case) #41
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem Statement
The learning-capture specification interview (
docs/specifications/learning-capture-specification-interview.md, "Edge Cases & Boundaries" section) explicitly mandates:capture_failed_command()atcrates/terraphim_agent/src/learnings/capture.rs:933does not honour this. The current pipeline is:There is no null-byte truncation step. When a captured command emits binary stderr (e.g. a Rust panic with binary debug bytes, a coredump path containing NUL, an
lddof a stripped ELF, a hex-dumped buffer), the entire byte sequence -- including unprintable bytes after the first\0-- is fed intoredact_secrets()and ultimately written to the markdown learning file. This produces:from_markdown()parsers.redact_secrets()regex passes that scan past the first\0and either miss redactions on the post-NUL tail or panic on non-UTF-8 sequences.annotate_with_entities()calls intoterraphim_automata::find_matches()with binary input, polluting the entity index with junk tokens.Verification:
The spec is unambiguous: store only the text portion. We have not implemented it.
Acceptance Criteria
truncate_at_null_byte(s: &str) -> &str(orCow<str>) incrates/terraphim_agent/src/learnings/capture.rsthat returns the slice up to the first\0, full input if none foundcapture_failed_command()callstruncate_at_null_byte(error_output)BEFOREredact_secrets(), so all downstream stages (redaction, annotation, file write) see only the text portion[binary output truncated at offset N]so the reviewer knows data was droppedparse_chained_command()is also passed truncatedcommandif the command itself contains\0(defensive; should not happen in practice but matches the spec's "raw string" intent)capture.rscover:error_outputwith no NUL byte (passthrough, no marker appended)error_outputwith NUL at offset 0 (empty text portion, marker appended)error_outputwith NUL mid-stream (text up to NUL plus marker)error_outputcontaining both a secret BEFORE the NUL and binary garbage AFTER (verify secret redacted, garbage dropped)error_outputwith multiple NUL bytes (only first matters)cargo test -p terraphim_agent learnings::capturepassescargo clippy -p terraphim_agent -- -D warningspassesProposed Approach
Crates affected:
crates/terraphim_agentonly.Files to modify:
crates/terraphim_agent/src/learnings/capture.rs:fn truncate_at_null_byte(s: &str) -> (&str, Option<usize>)returning the truncated slice and the offset at which truncation occurred (None = no NUL found).capture_failed_command()insert one call after the ignore/chain checks:commanddefensively beforeparse_chained_command.#[cfg(test)] mod testsblock at the bottom of the same file.Why this approach: Pure additive change inside one function. Keeps the redaction / annotation / write order unchanged. The marker line is plain ASCII so it round-trips through
CapturedLearning::to_markdownandfrom_markdownwithout escaping.Dependencies
None. Independent of #38 (config loading), #40 (auto-suggest), #24 (chained command isolation). May share a future config knob (
learnings.truncation.binary_marker) but that is out of scope for this PR.Verification
Manual smoke test (records a capture with embedded NUL and confirms file is text-only):
Scope
Single-file change (~40-60 lines of code + ~80 lines of tests). Single PR, well under the 500-line cap.
Upstream: This issue tracks a feature for terraphim/terraphim-ai