Expand description
A command-logging ProcessRunner decorator and its argv redaction.
LoggingRunner wraps any real ProcessRunner (the default JobRunner,
a ManagedClient’s inner runner, a test double) and reports every command it
runs — program, argv, working directory, exit code, and duration — to a
CommandObserver. Because it sits on the single seam every wrapper spawns
through, coverage is complete by construction: it observes all of vcs-git
/ vcs-jj / the forge wrappers without any per-call-site instrumentation, and
it can’t drift out of date when a new operation is added.
§Why this is a security boundary
Logging argv is delicate: the value slots can carry a PR/issue body, a commit
message, a clone URL, or — in principle — a secret. This module never emits a
value verbatim. redact_args applies a fail-closed policy before anything
reaches an observer:
- The value after a sensitive flag (
--token,--password,--secret,--authorization, …) is replaced with<redacted>, as is the value of a--flag=valueform of one. - A value that contains a secret shape (a
ghp_/github_pat_/glpat-/… token prefix, or anx-access-token:embed) is replaced with<redacted>. - A URL with userinfo (
scheme://user@host/…orscheme://user:pass@host/…) keeps its host/path but masks the userinfo (scheme://<redacted>@host/…). The conventional non-secretssh://git@host/…form remains visible. - Any long free-text value (a PR/issue body, a commit message) is truncated
to
MAX_VALUE_LENcharacters plus a length marker.
This is defence in depth on top of the workspace’s existing “the token never
rides in argv” contract (forge tokens travel in GH_TOKEN/GITLAB_TOKEN
environment, git’s secret via credential.helper) — the decorator never logs
the environment at all, so the token-carrying channel is out of scope for the
log by construction, and the argv redaction guards the residual risk.
The default StderrObserver writes a one-line summary to stderr, never
stdout — so a JSON-RPC transport sharing the process’s stdout (the vcs-mcp
server) stays a clean transport. Supply your own CommandObserver to route
the same structured record into tracing, a file, or a test buffer instead.
Structs§
- Command
Record - A display-safe, already-redacted record of one command a
LoggingRunnerran, handed to aCommandObserver. Every field is safe to print:argshas been throughredact_args, andstatuscarries no captured process output. - Logging
Runner - A
ProcessRunnerdecorator that reports every command it runs to aCommandObserver, then forwards the real runner’s result unchanged. - Stderr
Observer - The default
CommandObserver: writes one line per command to stderr (never stdout, so a stdout JSON-RPC transport stays clean), prefixed with a short tag. Format:`<tag>: <program> <args…> (cwd: <dir>) -> <status> in <dur>`.
Enums§
- Command
Status - How an observed command finished. Carries no captured stdout/stderr — only a coarse, allocation-free category — so an observer can never leak process output (which could echo user text) into a log.
Constants§
- MAX_
VALUE_ LEN - The longest a single free-text argv value is rendered before it is truncated
with a
…(<n> chars)marker. Normal argv (subcommands, flags, refs, paths, revsets) sits well under this, so it only ever clips genuinely large values — a PR/issue body, a long commit message — keeping the log both readable and free of bulk user text. The exact number is not load-bearing.
Traits§
- Command
Observer - A sink for the command records a
LoggingRunnerproduces. Implement it to route the (already-redacted)CommandRecordintotracing, a file, a metrics counter, or a test buffer; the built-inStderrObserverwrites a one-line summary to stderr.
Functions§
- redact_
args - Redact a command’s argv for display: mask secret-bearing values, mask the userinfo of a URL, and truncate long free text — see the module docs for the full policy. Returns one display string per input argument, in order. The policy is fail-closed: when in doubt it masks.
- redact_
value - Redact one free-text value without sequence-aware flag handling or truncation.