Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a023c0-a0a3-7c03-b395-5a0d2757214d
288 lines
12 KiB
Markdown
288 lines
12 KiB
Markdown
---
|
|
id: RMGR-WP-0009
|
|
type: workplan
|
|
title: "Coding-assistant commit provenance trailers"
|
|
domain: infotech
|
|
repo: repo-manager
|
|
status: active
|
|
owner: codex
|
|
topic_slug: infotech
|
|
created: "2026-08-21"
|
|
updated: "2026-08-21"
|
|
parent_project: prj-state-hub-retirement
|
|
related:
|
|
- RMGR-WP-0004
|
|
- RMGR-WP-0005
|
|
- ADR-007
|
|
- ADR-010
|
|
state_hub_workstream_id: "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:
|
|
|
|
```text
|
|
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
|
|
|
|
```task
|
|
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:
|
|
|
|
```text
|
|
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`, and `coulombcore` all 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 exports
|
|
`CLAUDE_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
|
|
|
|
```task
|
|
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
|
|
`--amend` and 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
|
|
|
|
```task
|
|
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:
|
|
|
|
```text
|
|
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
|
|
|
|
```task
|
|
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
|
|
|
|
```task
|
|
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
|
|
|
|
```task
|
|
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-Session` values 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
|
|
|
|
```task
|
|
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
|
|
|
|
- [x] Trailer contract published in `docs/repository-standards_v0.1.md`
|
|
- [x] Hook live via `core.hooksPath`; never blocks, idempotent, silent for humans
|
|
- [x] Claude, Codex, and Grok each emit trailers, or are recorded as unable with a reason
|
|
- [x] `git log` alone answers "which assistant, which model, which session"
|
|
- [ ] Overlapping-session report runs against at least one real collision window
|
|
- [x] Cutover commit recorded; pre-cutover history reported as unknown, not inferred
|