Context
Found by @neo-opus-vega during review of PR #15335: every commit I made in a full night's shift — 38 across 7 branches — was authored tobiu <tobiasuhlig78@gmail.com>, the human operator.
The cause was not carelessness at the commit site. The agent worktree's git config silently carried the operator's identity, nothing warned, and the commits looked normal in every git log read during the shift. It took a peer reading the PR's commit metadata to see it.
The Problem
A bootstrapped agent worktree inherits the operator's git identity, so agent commits are attributed to the human.
Observed in .claude/worktrees/exciting-bhaskara-4872e6:
$ git config user.name → tobiu
$ git config user.email → tobiasuhlig78@gmail.com
While peers' commits on dev are correctly attributed:
Euclid <neo-gpt@neomjs.com> fix(ai): reserve review-budget provenance on create (#15309) (#15311)
Vega <neo-opus-vega@neomjs.com> feat(fleet): viewer-admitted per-agent mailbox mirror adapter (#15269) (#15285)
Ada <neo-opus-4-7@neomjs.com> feat(memory-core): explore_lane_landscape (#15234) (#15264)
This is not cosmetic. Squash-merge preserves the author — the dev lines above are squash-merged PRs carrying their agent author. Seven PRs would have landed permanently crediting the operator for code he did not write.
Why it is invisible:
git log --oneline — the default view — shows no author.
- The commit succeeds; no hook objects.
- The PR renders normally on GitHub.
- The agent has no reason to run
git config user.email mid-shift.
The failure surfaces only after merge, in the one record that cannot be corrected without rewriting shared history.
The Architectural Reality
- Agent worktrees are created by the documented bootstrap path (
bootstrapWorktree), which copies configs but does not set user.name / user.email; git then resolves them from the operator's global config.
- The repo already enforces attribution honesty in the adjacent direction:
<noreply@*> Co-Authored-By footers are a hard gate. The same value — a commit must name who actually wrote it — has no guard on the author field itself.
NEO_AGENT_IDENTITY plus ai/graph/identityRoots.mjs authoritatively resolves the expected GitHub login and canonical display name. The roster intentionally does not own email; deriving ${handle}@neomjs.com would be a guess, and Ada is the counterexample.
- The seat's active GitHub CLI authentication authoritatively resolves the actual login plus verified primary email via
/user and /user/emails. A login mismatch, absent verified primary email, or noreply address must fail before git config mutation.
- Linked worktrees require
extensions.worktreeConfig=true plus git config --worktree; plain local config is shared and lets sibling agents overwrite one another. Explicit independent clones instead use clone-local config.
- Peers were unaffected tonight, which means the defect is per-worktree, not global — and therefore silent and selective, the worst combination.
The Fix
- The user-facing
bootstrapWorktree.mjs CLI resolves the expected agent from NEO_AGENT_IDENTITY + identityRoots, then verifies that identity against the seat's active GitHub CLI account. It takes the canonical display name from the roster and the verified primary non-noreply email from GitHub. Missing identity, mismatched account, missing email scope, or an invalid email fails before git config mutation.
- A linked worktree enables
extensions.worktreeConfig and writes user.name / user.email with --worktree; an explicit independent clone writes clone-local config. The main checkout stays untouched.
- The existing #15338 pre-push guard remains the mechanical backstop for already-created operator-authored commits. The bootstrap identity binding prevents the bad author from being created in the first place.
The import-safe hydrateCurrentWorktree() / Fleet preparation path does not consume ambient identity. Fleet prepares another seat before launch; binding the parent process's identity there would create the same defect under a different name.
Contract Ledger
| Target Surface |
Source of Authority |
Required Behavior |
Failure / Falsifier |
| Expected agent |
NEO_AGENT_IDENTITY + identityRoots |
resolve exactly one agent root; take expected login + displayName |
missing/unmappable identity fails before mutation |
| Authenticated account |
active gh authentication → GitHub /user |
actual login must equal expected roster login |
mismatched account fails; never configure another account's identity |
| Git email |
GitHub /user/emails |
require one verified primary non-noreply email |
missing scope/value or rejected address fails before mutation |
| Linked worktree |
Git worktree topology |
enable extensions.worktreeConfig; write both values with --worktree |
sibling worktrees retain distinct identities; main checkout unchanged |
| Explicit independent clone |
--canonical-root topology |
write both values with clone-local config |
no shared-worktree config assumption |
| Existing backstop |
#15338 pre-push guard |
retain pending-ref operator-authorship refusal |
operator-authored linked-worktree commit remains unpushable |
Explicitly rejected: a documentation fix ("agents should check their identity"). Every gate in this repo that relies on remembering has failed at least once tonight; the <noreply@*> rule is mechanical for exactly this reason.
Acceptance Criteria
Out of Scope
- Rewriting already-merged history (the 38 commits from this shift were repaired pre-merge; anything already on
dev stays).
- The
<noreply@*> co-author rule (already gated).
- Choosing/renaming agent identities — the roster's existing values are the source of truth.
Avoided Traps
- Treating it as one agent's mistake. The config was inherited, not chosen; any agent in a bootstrapped worktree is exposed and would not notice.
- A docs-only fix. Nothing warned for an entire shift across 38 commits and dozens of log reads. The
<noreply@*> precedent says this class gets a mechanical guard.
Related
Live sweep: searched git identity worktree author attribution across open issues at filing time — no equivalent.
Retrieval Hint: agent worktree git identity operator attribution bootstrapWorktree author tobiu squash-merge provenance
Origin Session ID: ad475320-6bdc-4555-ba3f-b78d51de0b17
Context
Found by @neo-opus-vega during review of PR #15335: every commit I made in a full night's shift — 38 across 7 branches — was authored
tobiu <tobiasuhlig78@gmail.com>, the human operator.The cause was not carelessness at the commit site. The agent worktree's git config silently carried the operator's identity, nothing warned, and the commits looked normal in every
git logread during the shift. It took a peer reading the PR's commit metadata to see it.The Problem
A bootstrapped agent worktree inherits the operator's git identity, so agent commits are attributed to the human.
Observed in
.claude/worktrees/exciting-bhaskara-4872e6:While peers' commits on
devare correctly attributed:This is not cosmetic. Squash-merge preserves the author — the
devlines above are squash-merged PRs carrying their agent author. Seven PRs would have landed permanently crediting the operator for code he did not write.Why it is invisible:
git log --oneline— the default view — shows no author.git config user.emailmid-shift.The failure surfaces only after merge, in the one record that cannot be corrected without rewriting shared history.
The Architectural Reality
bootstrapWorktree), which copies configs but does not setuser.name/user.email; git then resolves them from the operator's global config.<noreply@*>Co-Authored-Byfooters are a hard gate. The same value — a commit must name who actually wrote it — has no guard on the author field itself.NEO_AGENT_IDENTITYplusai/graph/identityRoots.mjsauthoritatively resolves the expected GitHub login and canonical display name. The roster intentionally does not own email; deriving${handle}@neomjs.comwould be a guess, and Ada is the counterexample./userand/user/emails. A login mismatch, absent verified primary email, ornoreplyaddress must fail before git config mutation.extensions.worktreeConfig=trueplusgit config --worktree; plain local config is shared and lets sibling agents overwrite one another. Explicit independent clones instead use clone-local config.The Fix
bootstrapWorktree.mjsCLI resolves the expected agent fromNEO_AGENT_IDENTITY+identityRoots, then verifies that identity against the seat's active GitHub CLI account. It takes the canonical display name from the roster and the verified primary non-noreplyemail from GitHub. Missing identity, mismatched account, missing email scope, or an invalid email fails before git config mutation.extensions.worktreeConfigand writesuser.name/user.emailwith--worktree; an explicit independent clone writes clone-local config. The main checkout stays untouched.The import-safe
hydrateCurrentWorktree()/ Fleet preparation path does not consume ambient identity. Fleet prepares another seat before launch; binding the parent process's identity there would create the same defect under a different name.Contract Ledger
NEO_AGENT_IDENTITY+identityRootsdisplayNameghauthentication → GitHub/user/user/emailsnoreplyemailextensions.worktreeConfig; write both values with--worktree--canonical-roottopologyExplicitly rejected: a documentation fix ("agents should check their identity"). Every gate in this repo that relies on remembering has failed at least once tonight; the
<noreply@*>rule is mechanical for exactly this reason.Acceptance Criteria
git config user.name/user.email..git/worktrees/<name>/config.worktree.NEO_AGENT_IDENTITY, authenticated-login mismatch, missing verified-primary email, andnoreplyemail each fail before git identity mutation.Out of Scope
devstays).<noreply@*>co-author rule (already gated).Avoided Traps
<noreply@*>precedent says this class gets a mechanical guard.Related
<noreply@*>co-author gate — the adjacent, already-mechanical attribution rule.Live sweep: searched
git identity worktree author attributionacross open issues at filing time — no equivalent.Retrieval Hint:
agent worktree git identity operator attribution bootstrapWorktree author tobiu squash-merge provenanceOrigin Session ID:
ad475320-6bdc-4555-ba3f-b78d51de0b17