Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
12 KiB
| id | type | title | domain | repo | status | owner | topic_slug | created | updated | parent_project | related | state_hub_workstream_id | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| RMGR-WP-0009 | workplan | Coding-assistant commit provenance trailers | infotech | repo-manager | active | codex | infotech | 2026-08-21 | 2026-08-21 | prj-state-hub-retirement |
|
0516ce7b-c0fe-4555-bc31-eb7943c163ef |
Coding-assistant commit provenance trailers
Goal
Make commits written by coding assistants self-identifying — which assistant,
which model, which process — so that "who changed this and did two sessions
collide?" is answerable from git log alone.
The gap
Observed 2026-08-21 while auditing state-hub. The repo has exactly two commit
identities:
tegwick <bernd.worsch@gmail.com> 120 of last 200
custodian-sync <custodian-sync@railiance.local> 80 of last 200
Claude Code commits are identifiable only because they carry a
Co-Authored-By: Claude Opus 5 trailer, added by a prompt instruction in
~/.claude/CLAUDE.md. Codex and Grok add nothing. So a commit made by Codex is
byte-identical in provenance to one typed by hand, and the question "which
assistant made this change" has no answer.
This is not hypothetical. On 2026-08-21 a Codex session ran for ~14 hours on the workstation while a Claude session worked the same repo. Establishing that they had not collided required correlating process tables against commit timestamps and a trailer only one of them writes — and the conclusion could only be stated as "not me and not the sync bot", never as "this was Codex".
It also weakens the multi-writer work already underway. ADR-007 decision 2 and
ADR-010 are about several writers over one artifact set; per-writer provenance
is the evidence those decisions need and currently lack.
Category boundary — read before implementing
Three distinct kinds of non-human commit exist or are coming. Only the first is in scope here. The scheme must not foreclose the others.
| Category | Identity model | Status |
|---|---|---|
| Coding assistant | Runs under Bernd's account; author stays tegwick. Interactive, returns to the operator for instruction. |
This workplan |
| Kaizen agent | Autonomous; will have its own agent id and account, and should commit as itself. | Future, not here |
| Scheduled / OpenRouter capacity | Unattended runs against purchased inference capacity. | Separate category, not here |
The author is deliberately not changed. Coding assistants commit as Bernd because he is supervising, and that placement of responsibility is the point — it is not an accident to be corrected. These trailers add provenance, not a second actor. A future kaizen agent committing under its own identity is a different mechanism and a different decision.
Define the trailer contract
id: RMGR-WP-0009-T01
status: done
priority: high
state_hub_task_id: "39ac4f86-a936-4726-8ad2-d6219f24b1df"
Publish the trailer set as a repository standard in
docs/repository-standards_v0.1.md — repo-manager owns repository standards
under decision 747011c6.
Requested core fields are model and process. Proposed shape, using git
trailers so git interpret-trailers and git log --format=%(trailers) parse
them without custom tooling:
Assistant: claude-code
Assistant-Model: claude-opus-5
Assistant-Process: 208772@bnt-lap001
Assistant-Session: 354884ba-6e26-4918-8bcc-4fc675e419ee
Two fields beyond the request, both cheap and both load-bearing for the stated purpose — cut them if they are not wanted, the scheme works without:
@<host>on the process. A bare PID is ambiguous across the fleet;bnt-lap001,railiance01, andcoulombcoreall allocate from the same small number space. Without the host, two unrelated sessions can read as one.Assistant-Session. PIDs are recycled by the OS and mean nothing once the process exits, so a PID alone cannot group a session's commits after the fact — which is exactly the collision question. Claude Code already exportsCLAUDE_CODE_SESSION_ID; if Codex or Grok expose no equivalent, omit the field for that tool rather than inventing one.
Decide also whether Co-Authored-By: Claude Opus 5 is kept, folded into
Assistant-Model, or dropped. Keeping both duplicates the model.
Result (2026-08-21): the contract is published in
docs/repository-standards_v0.1.md. Assistant is required when detected;
model, host-qualified process, and session are conditional on availability.
Assistant-Model is canonical and assistant-specific Co-Authored-By
conventions are deprecated.
Implement the hook
id: RMGR-WP-0009-T02
status: done
priority: high
state_hub_task_id: "7aaccdde-b79f-43ca-b1cd-81c7c3153229"
A prepare-commit-msg hook that appends the trailers from environment, wired
fleet-wide via core.hooksPath so it applies without per-repo installation.
There are no git hooks anywhere today — core.hooksPath is unset globally and
locally, and no repo has a non-sample hook — so this is greenfield and cannot
conflict with existing tooling.
Requirements:
- Never block a commit. Missing or unreadable environment means write no trailer, not fail. A provenance mechanism that stops work will be disabled.
- Idempotent. Appending must not double a trailer already present, so
--amendand rebases stay clean. - Silent for humans. A commit typed by Bernd with no assistant environment set gets no trailers and looks exactly as it does today.
- Detection is from environment, not process-tree walking — the hook runs several levels below the agent and the tree is not reliable.
Prefer core.hooksPath over per-repo .git/hooks (not committable, lost on
clone) and over committed hook files (needs opt-in per repo).
Result (2026-08-21): .githooks/prepare-commit-msg is installed globally via
core.hooksPath=/home/worsch/repo-manager/.githooks. Tests prove human silence,
exact-token idempotency, and non-blocking behavior. Tool-neutral overrides are
ASSISTANT_NAME, ASSISTANT_MODEL, ASSISTANT_PROCESS, and
ASSISTANT_SESSION.
Wire Claude Code
id: RMGR-WP-0009-T03
status: done
priority: high
state_hub_task_id: "f7a29cca-d76c-4205-a33b-3a2276459fce"
Environment is already sufficient, verified 2026-08-21:
CLAUDECODE=1
CLAUDE_CODE_SESSION_ID=<uuid>
CLAUDE_PID=<pid>
CLAUDE_CODE_ENTRYPOINT=cli
Only the model is missing — CLAUDE_EFFORT is exported but the model id is
not. Resolve from ~/.claude/settings.json model, or have the hook accept an
ASSISTANT_MODEL override the session sets. Note that settings.json records
the configured model, which is not always the model that served a given turn.
Once live, retire the Co-Authored-By prompt instruction in ~/.claude/CLAUDE.md
per T01's ruling — a hook cannot be forgotten mid-session the way a prompt
instruction can, which is the main reason to prefer it.
Result (2026-08-21): the hook detects CLAUDECODE, session id, and PID, accepts
the neutral overrides, and uses ~/.claude/settings.json's configured model as
a fallback. No Co-Authored-By instruction remains in the current global
Claude instructions, so no external prompt edit was needed.
Wire Codex
id: RMGR-WP-0009-T04
status: done
priority: high
state_hub_task_id: "180e50ff-6d43-46f3-84d2-b5337ff73900"
Discover what the Codex CLI exports — no CODEX_* variables were present in the
Claude session's environment on 2026-08-21, so this needs checking from inside a
Codex session rather than assumed.
Codex is the case that motivates the workplan: it is the assistant currently
invisible in git log, and the one that ran 14 hours alongside a Claude session.
If it exports nothing usable, set the variables in its launch wrapper. A stable per-session value is worth more than the exact PID if only one is available.
Result (2026-08-21): the active Codex CLI exports CODEX_SESSION_ID and
CODEX_THREAD_ID; the hook uses the former as the stable session and reads the
configured model from $CODEX_HOME/config.toml (or ~/.codex/config.toml) when
no exact neutral override is present. Codex's published stable environment
variable reference does not promise those session variables, so they remain a
best-effort compatibility input rather than the contract.
Wire Grok
id: RMGR-WP-0009-T05
status: done
priority: medium
state_hub_task_id: "b8451fe8-233f-4862-a238-5ee84901e7fb"
Same discovery for Grok. Lowest priority of the three only because it appears least in current history — confirm that is still true before deciding.
Result (2026-08-21): the installed Grok client exposes GROK_SESSION_ID and
GROK_DEFAULT_MODEL integration points; the hook consumes both and also accepts
the neutral override contract. No Grok process was active during cutover, so
its next real commit remains the operational proof rather than inferred history.
Report on assistant activity and collisions
id: RMGR-WP-0009-T06
status: done
priority: medium
state_hub_task_id: "a28d9287-b488-4946-bf46-6cad1a392778"
The trailers are the input; this is the payoff. An rmgr report over
git log --format=%(trailers) answering:
- which assistant and model touched a repo, over a window;
- overlapping sessions — two
Assistant-Sessionvalues with interleaved commits in one repo, which is the collision signal asked for; - commits with no trailer, split into human (expected) and assistant-not-yet-wired (a gap to close).
Keep it read-only over git. Do not add a hub table: this is derivable from the
repositories, and state-hub is being retired under STATE-WP-0079.
Result (2026-08-21): rmgr assistant-provenance report derives assistant,
model, process, session, unattributed buckets, and interleaved-session pairs
from Git history only. Regression coverage includes an A→B→A interleave.
Handle existing history
id: RMGR-WP-0009-T07
status: done
priority: low
state_hub_task_id: "e4ff1ab5-3507-4dff-bb0d-3fed3e0be80f"
History is not rewritten — the same reasoning as ADR-007 § Migration option 2.
Commits before the cutover stay unattributed, and the report must present that
honestly as "unknown", never infer an assistant from timing or message style.
Record the cutover commit so the report can distinguish "before provenance existed" from "assistant not wired". Without that boundary every pre-cutover commit reads as a gap.
Also decide the custodian-sync identity's treatment: it is automation with its
own committer identity already, so it likely needs no trailer — but it should be
named in the standard so it is not later mistaken for an unwired assistant.
Result (2026-08-21): history remains untouched and custodian-sync is
declared known automation exempt from assistant trailers. The configuration and
report distinguish before/after cutover at first attributed commit
329af60753b20896f30f098e8f75074652780032. A naturally occurring
multi-session window remains an acceptance proof for the plan, not a reason to
misclassify the cutover task as incomplete.
Acceptance
- Trailer contract published in
docs/repository-standards_v0.1.md - Hook live via
core.hooksPath; never blocks, idempotent, silent for humans - Claude, Codex, and Grok each emit trailers, or are recorded as unable with a reason
git logalone answers "which assistant, which model, which session"- Overlapping-session report runs against at least one real collision window
- Cutover commit recorded; pre-cutover history reported as unknown, not inferred