Coding Agent Command Execution Receipts: Verifiable Transcripts, Streaming Redaction, and Truncation Proofs
A line of text in a terminal doesn't mean a command actually ran in the approved way. This article defines an immutable receipt for command argv, cwd, executable, policy decision, exit status, and output digest, while preventing secrets from spreading through logs.
Key Points
- Topic Focus: AI Agent、TypeScript、DevOps
- Suitable for: indie hackers, full-stack engineers, AI application developers
- Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
A programming Agent often shows pnpm test in its reply, yet the platform cannot answer: was it a shell-interpreted string or an argument array, where did the actual binary come from, was the working directory correct, what did policy allow, was output truncated, and did it exit normally or get killed by a signal? Treating a chat code block as the execution record makes unrun commands indistinguishable from real execution.
Coding Agent Change Validation Gate defines which checks a patch needs, and MCP Audit Logging Design discusses tool-call auditing; this article only proves how one local process actually started and ended. It does not judge whether the development environment matches anywhere else, nor does it perform environment drift pre-checks.
Freeze command-receipt.v1
The executor must capture facts at the process-creation boundary rather than letting the Agent fill them in afterward. The contract for one attempt includes receiptId, parent task and step, argv, logical and resolved cwd, resolved executablePath, executable digest, interpreter or script digest, environment variable name allowlist, policyDecisionId and policy digest, sandbox profile, start/end monotonic time, PID, exit code or signal, and stream digests of stdout/stderr.
{
"schema": "command-receipt.v1",
"argv": ["pnpm", "test", "--", "auth.spec.ts"],
"cwd": {"requested": "./app", "resolved": "/workspace/app"},
"executable": {"path": "/usr/local/bin/pnpm", "sha256": "..."},
"policy": {"decisionId": "pd_91", "digest": "...", "outcome": "ALLOW"},
"termination": {"exitCode": 1, "signal": null}
}
The argument array and shell=true must be distinguished. The latter additionally records the shell executable, a controlled digest of the original script, and shell arguments; redirections, pipes, or command substitution must not be split into a fake argv. Linux execve describes the process boundary of program path, arguments, and environment; the platform should issue STARTED at the corresponding spawn/exec wrapper layer and then append TERMINATED. A mere REQUESTED cannot prove a process ever started.
cwd records both the caller's requested value and the directory object identity; it must not first resolve a string and then call an ordinary chdir. The Linux launcher uses a constrained openat2 to obtain a dirfd, records the mount ID, device, and inode, and has the child process perform fchdir on the same fd. The executable also must not be reopened by PATH between "computing the digest" and exec: first obtain a policy-constrained fd, read and digest from an immutable snapshot, then use fexecve or execveat(AT_EMPTY_PATH) on the same object. Recording only device/inode still cannot block concurrent rewrites; it should rely on a read-only content-addressed copy, fs-verity, or an equivalent sealing mechanism. Platforms that cannot provide object binding must mark UNBOUND_EXECUTABLE, and such a receipt cannot back a hard gate.
A shebang script should treat the script and interpreter as one execution tuple: pin their content and identity separately, then explicitly launch the pinned interpreter; shell=true likewise pins both the shell and the script text, and must not let the kernel or PATH re-select an interpreter after the fact. The environment by default retains only variable names and policy classification; values do not enter the receipt. When a non-sensitive config genuinely needs proving, store a pre-approved normalized digest. Credentials, cookies, and low-entropy tokens do not become automatically safe just because they are hashed.
Output Must Be Redacted While Being Read
stdout and stderr are read as independent numbered streams, and each chunk carries a sequence, original byte count, redacted byte count, and the chain digest of the previous chunk. The redactor must run before persistence, the event bus, and telemetry export, and retain a maximum pattern window across chunks; otherwise sk-abc split into sk- and abc would bypass the rule. The default-deny principle from Prompt and Trace Telemetry Governance applies equally, but the command receipt additionally must prove which bytes were not retained.
Record the redactionRuleset, hit types, and replacement counts; do not record the secrets that were hit. To prevent low-entropy values from being enumerated via digests, the raw stream does not produce public bare hashes; integrity uses an HMAC with an isolated key, and ordinary reviewers see only the SHA-256 of the redacted stream. If a rule errors, the output pipeline fails closed: it pauses persistence and marks the attempt as EVIDENCE_BLOCKED, and must not degrade to passing the raw text through.
Truncation Must Also Be a Verifiable Fact
A high-noise command may produce several GB of output. Once the quota is reached, you cannot just write .... The receipt keeps, for stdout and stderr respectively, observedBytes, retainedBytes, droppedBytes, the retained head/tail ranges, the truncation reason, and the HMAC of the full stream; the background continues draining until the process ends, or explicitly records that reading stopped and the process was terminated to protect resources. This way, "the command failed but the error line was truncated" is not misreported as no output.
The exit status must be three-way: normal exit code, signal/timeout termination, and startup failure. After a timeout, the two moments of sending the termination signal and then force-killing both enter the event sequence; when the executor crashes and cannot confirm the child process outcome, it marks UNKNOWN_TERMINATION and is forbidden from guessing 0. How test results bind to the current diff is the validation gate's job; this receipt only provides trustworthy command facts.
Immutable Does Not Mean Store Forever
Completed receipts are chained within a task via previousReceiptDigest and signed by the executor identity or written to object storage with a retention lock. The W3C PROV activity/entity relationship can connect policy, process, and output artifacts; updating redaction rules can only issue derived views and must not overwrite old receipts. When deleting expired redacted bodies, retain minimal metadata, the chain digest, a deletion proof, and the expiry, so "immutable" does not become an excuse to keep sensitive data forever.
When a reviewer clicks a command in the reply, they should see the receipt status, not editable text. Missing TERMINATED, a policy digest mismatch, an invalid signature, blocked output evidence, or a truncation method that does not satisfy this gate are all displayed as unverified. Coding Agent Sandbox Permissions Design decides what a command can reach; this receipt must not derive one allowance into subsequent unlimited authorization.
Fault Injection, Quantitative Gates, and Rollback
Before launch, inject six classes of faults: a secret split across two chunks; stdout flooding that triggers truncation; a process killed by SIGKILL; a PATH target replaced or the same inode rewritten after the digest; a cwd symlink changing between request and launch; and an executor crashing after STARTED. The expected outcomes are, respectively: zero secrets persisted, accurate dropped bytes, signal termination, executing the sealed copy or launch being blocked, an unchanged dirfd object identity, and UNKNOWN_TERMINATION, with the chain validation still locating the missing stage.
The hard gates are set to: 100% managed-command receipt coverage, 100% completeness of the required argv/cwd/executable/policy/termination fields, zero cross-chunk synthetic secret leakage, zero byte-accounting discrepancy under a 10 GiB stress test, 100% tamper detection, and a signing-induced p95 startup latency increase under 20 ms. Any missing key field must not support the claim that "the check ran".
At release, first shadow-receipt 5% of non-sensitive commands without affecting the original execution; after meeting the threshold for seven consecutive days, expand to 50%, then enforce only the high-risk merge gate. A rollback reverts to the previous collection and redaction policy and stops new hard judgments, but keeps the already-issued receipts and the versioned reader; if the new writer is incompatible with the old reader, dual-write and verify before switching. The ultimate goal is not to save more terminal text, but to make every "I ran it" correspond to a verifiable, non-leaking execution fact that honestly expresses its gaps.
Related
Related Content
Article
What Is Vibe Coding? Will It Change How Programmers Work?
Technical trends, engineering practices, and an indie developer perspective on Vibe Coding, AI, and independent development.
Article
From Copilot to Codex: AI Coding Tools Enter the Agent Era
Technical trends, engineering practices, and indie-developer perspectives around AI, AI Agents, and Vibe Coding.
Article
Why Does MCP Matter? Understanding the AI Tool Protocol
Technical trends, engineering practices, and an independent developer perspective on MCP and AI Agents.
Article
Core Architecture of AI Agents: Planning, Memory, Tools, and Execution
Technical trends, engineering practices, and independent developer perspectives around AI Agents and large language models.