OTel: git_commit_id is scraped from git-commit stdout — silently dropped by `| tail`/`-q`/redirection, or by the `git -C <path> commit` form
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report
- [x] I am using a recent version of Claude Code (2.1.209)
What's Wrong?
With OpenTelemetry enabled (CLAUDE_CODE_ENABLE_TELEMETRY=1, OTEL_LOGS_EXPORTER=otlp, OTEL_LOG_TOOL_DETAILS=1), the claude_code.tool_result log event for a successful git commit is documented to include git_commit_id:
monitoring-usage docs: "For Bash tool: includesbash_command,full_command,timeout,description,dangerouslyDisableSandbox, andgit_commit_id(the commit SHA, when a git commit command succeeds)."
In practice git_commit_id is silently absent for many successful commits. It is not attached from the fact that a commit happened — it is scraped out of the command's stdout by matching git's [<ref> <sha>] summary line, behind a command-string gate. So it disappears in two broad situations, several of which are common in real/agent workflows:
gitandcommitare not adjacent in the command — e.g.git -C <path> commit …. The command-string gate is/\bgit\s+commit\b/, and the-C <path>between the two words fails it.- The
[<ref> <sha>]summary line is not present in the captured stdout, e.g.:
git commit -q/--quiet(git prints no summary line),- stdout redirected/suppressed (
git commit … >/dev/null), - the output is piped/filtered so the summary line is dropped — most commonly
git commit … 2>&1 | tail -N(or| grep,| head). The summary is git's first output line, so on a multi-file commit| tail -Ntruncates it away andgit_commit_idvanishes.
The original report of this issue attributed the loss specifically to the git -C <path> form and said "the && chaining is not the trigger." That is only half the picture: chaining is indeed not a trigger (chaining before the commit, e.g. git add && git commit, keeps git_commit_id), but the stdout-scrape (gate #2) is a second, larger source of silent loss that the original report did not cover.
What Should Happen?
Per the documentation, git_commit_id should be present for any successful git commit, independent of how the command is spelled (path form, quiet flag, redirection, piping through tail/grep, etc.). A commit's SHA is knowable without scraping human-readable stdout (e.g. git rev-parse HEAD in the tool's cwd, or git -C <path> rev-parse HEAD), so the telemetry field should not depend on the [<ref> <sha>] line surviving in stdout.
Root Cause (from the bundled CLI, v2.1.209)
The extraction is a pure function of the command string and its stdout. Both regexes below are taken verbatim from the shipped binary:
// gate 1 — command string must match this (note: `git -C <path> commit` does NOT match):
E.command.match(/\bgit\s+commit\b/)
// … and stdout must be present, then:
K.git_commit_id = SYi(String(ce.data.stdout)) // only set if truthy
// gate 2 — SYi scrapes the SHA out of git's summary line:
function SYi(e){ return e.match(/\[[\w./-]+(?: \(root-commit\))? ([0-9a-f]+)\]/)?.[1] }
git_commit_id is attached iff gate 1 (command) AND gate 2 (a [<ref> <sha>] line somewhere in stdout) both hold. Anything that removes the summary line from stdout, or that separates git from commit, silently drops the field while the commit still succeeds and the tool_result event still fires.
Behavior matrix (verified on 2.1.209)
Extraction depends only on (command string, stdout); each row applies the two shipped regexes to the real stdout the command produces.
Gate 1 — command adjacency (/\bgit\s+commit\b/):
| Command | git_commit_id |
|---|---|
| git commit --allow-empty -m x (bare) | ✅ present |
| git commit … (extra whitespace) | ✅ present |
| git commit … && echo done (chain AFTER) | ✅ present |
| git add -A && git commit … (chain BEFORE) | ✅ present |
| git add -A ; git commit … (chain BEFORE, ;) | ✅ present |
| cd <path> && git commit … (cd prefix) | ✅ present |
| echo x && git commit … (non-git prefix) | ✅ present |
| heredoc cat > msg <<EOF … EOF then git commit -F msg (unpiped) | ✅ present |
| git -C <path> commit … | ❌ absent (gate 1 fails) |
| git -C <path> add … && git -C <path> commit … | ❌ absent (gate 1 fails) |
Gate 2 — the [<ref> <sha>] summary line must survive in stdout:
| Command | git_commit_id |
|---|---|
| git commit … 2>&1 (no pipe) | ✅ present |
| git commit … 2>&1 \| tail -4 on a small commit (summary fits) | ✅ present |
| git commit -q … / --quiet | ❌ absent (no summary line) |
| git commit … >/dev/null | ❌ absent (stdout suppressed) |
| git add -A && git commit … 2>&1 \| tail -4 on a multi-file commit | ❌ absent (summary truncated away) |
| git commit … 2>&1 \| tail -1 on a multi-file commit | ❌ absent (summary not in last line) |
(The summary line is emitted on stdout — confirmed: git commit → stdout [master 541c84b] streamcheck, stderr empty.)
Steps to Reproduce
- Enable telemetry with tool details, pointing the OTLP exporter at a receiver you can inspect:
````
CLAUDE_CODE_ENABLE_TELEMETRY=1
OTEL_LOGS_EXPORTER=otlp
OTEL_LOG_TOOL_DETAILS=1
OTEL_EXPORTER_OTLP_ENDPOINT=<an OTLP collector you can read /v1/logs from>
- In a git repo with several unstaged files, have Claude run these as separate Bash commands:
git commit --allow-empty -m ok→git_commit_idpresentgit add -A && git commit -m ok→git_commit_idpresent (chaining before is fine)git -C . commit --allow-empty -m nope→git_commit_idabsentgit commit -q --allow-empty -m nope→git_commit_idabsentgit add -A && git commit -m nope 2>&1 | tail -4(with >4 files changed) →git_commit_idabsent
- Inspect the exported
claude_code.tool_resultrecords — the field is present/absent per the matrix above, with no error surfaced in any case.
Impact
git_commit_id is the only structured link between a Claude-run commit and its SHA in OTel. Commit-attribution pipelines built on it undercount silently, and the loss is biased toward exactly the workflows people build for automation:
- Path form (
git -C <path> commit) — worktrees, multi-repo, agents committing to an explicit path. - Piped/filtered output (
git commit … 2>&1 | tail -N,| grep) — an extremely common agent idiom for keeping tool output short; on any multi-file commit it truncates the summary line and drops the SHA. - Quiet / redirected commits (
-q,>/dev/null).
None of these surface an error, so an attribution pipeline reads them as "no commit happened."
Claude Model
N/A — behavior is independent of the model.
Is this a regression?
Unknown — not tested against earlier versions.
Last Working Version
Unknown.
Claude Code Version
2.1.209 (also reported on 2.1.207; extraction logic unchanged)
Platform
CLI (terminal)
Operating System
macOS (Darwin)