docs(agents): agents author their commits as Claude (<BEADS_ACTOR>) (improvise-1xfb.3)

The rule sits beside the claim in agent-rules.md, and the brief checklist
in wave/SKILL.md ties the actor to it. The evidence entry records why the
wrapper sets GIT_AUTHOR_NAME and not user.name, and its two rivals.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
Claude (agent-1xfb3)
2026-09-24 20:19:00 -07:00
committed by Edward Langley
parent eff92f3e3c
commit 256057b7f8
3 changed files with 58 additions and 1 deletions
+4 -1
View File
@@ -52,7 +52,10 @@ nobody corrects. The brief adds only what varies per wave:
suite, which it is not. The agent still measures its own.
3. **Its ground, and the ground that is not.** Name any orchestrator-only file
it is exceptionally allowed to edit.
4. **Its `BEADS_ACTOR` and its tickets.**
4. **Its `BEADS_ACTOR` and its tickets.** The actor also names the agent's
commits: it commits through `scripts/agent-git`, which authors each one as
`Claude (<BEADS_ACTOR>)` (`agent-rules.md`, "Discipline per ticket"). So
give each agent its own actor, and do not tell it to set `user.name`.
5. **The attribution line, commit trailers and PR footer**, spelled out.
They name the agent (Claude's line opens `_Posted by Claude (<model>)…_`)
and this session's URL, which is why the shared `agent-rules.md` cannot
+24
View File
@@ -216,6 +216,30 @@ out on purpose:
- **Claim first:** `export BEADS_ACTOR=<your actor>` in every shell, then
`bd update <id> --claim`. Exit 13 means another actor holds it. Pick another;
do not retry.
- **Author every commit as `Claude (<BEADS_ACTOR>)`.** The user: "agents
should override git author name, I think to include an agent identifier
(matching BEADS_ACTOR)" (2026-09-25; improvise-1xfb.3). Make every commit
through the wrapper, merge commits included:
`BEADS_ACTOR=<your actor> scripts/agent-git commit …`, and the same for
`merge` or `cherry-pick`. The wrapper sets the author name for that one
command, and it refuses (exit 2) when `BEADS_ACTOR` is unset or empty. It
reads `BEADS_ACTOR` from its own environment, so the prefix works in a shell
that keeps nothing between commands.
- **The author email, the committer and the `Co-Authored-By` trailer stay as
they are.** The email is the owner's configured one, so GitHub still links
the commit.
- **Never set `user.name` with `git config`.** Worktrees share
`.git/config`, so it renames every agent at once. Do not use
`git -c user.name=…` either: `user.name` names the committer as well as
the author. Why the wrapper uses `GIT_AUTHOR_NAME`:
`context/test-evidence/tooling.md`.
- A plain `git commit --amend` keeps the author it amends, so amend with
plain git or with the wrapper.
- Before you push, check `git log --format='%an <%ae> | %cn' <pinned
SHA>..HEAD`. Every line must name your actor.
- **The format is a proposal.** `Claude (<BEADS_ACTOR>)` is the default
proposed on improvise-1xfb.3's PR. The user may change it there. The format
lives in `scripts/agent-git` and nowhere else.
- **`bd` needs `.envrc` allowed in your worktree.** Without it you get a
**version mismatch**, not a clean failure. Run `direnv allow` in the worktree,
or rely on the `[whitelist] prefix` in `~/.config/direnv/direnv.toml`.
+30
View File
@@ -1772,3 +1772,33 @@ The output includes the user's inline answers on
**Scope sweep.** `scripts/pr-watch` passes its JSON through `printf '%s' | jq`,
never as an argument. `grep -rn 'argjson\|--arg '` over `scripts/`,
`.agents/` and `.claude/skills/` finds no other data argument.
### An agent authors its commits, and the committer is left alone — `scripts/agent-git-test.sh` (improvise-1xfb.3)
**What was missing.** Every agent commit was authored as the repository owner,
so history could not say which agent wrote what. `scripts/agent-git` runs one
git command with `GIT_AUTHOR_NAME="Claude ($BEADS_ACTOR)"` and refuses, exit 2,
when `BEADS_ACTOR` is unset or empty.
**Why not `git -c user.name=…`,** the mechanism the ticket and the brief both
offered first. `user.name` is the default for the committer as well as the
author, so it renames both. The ticket asks for the committer to be left
alone. Probed in a scratch repo: a `-c user.name` commit reads
`Claude (u) <o@x> | Claude (u)` as author and committer. A `GIT_AUTHOR_NAME`
commit reads `Claude (t) <o@x> | Owner`. The same probe showed that a merge
takes `GIT_AUTHOR_NAME` too, and that a plain `--amend` keeps the author it
amends.
**Rivals.**
- **`exec git -c user.name="Claude ($BEADS_ACTOR)" "$@"`**: 10 passed,
1 failed, "the committer is untouched" (got `Claude (agent-a1)`).
- **The refusal removed** (`if false`): 7 passed, 4 failed. They are "an unset
BEADS_ACTOR is refused" (exit 1 from `set -u`, not 2), "the refusal names
BEADS_ACTOR", "an empty BEADS_ACTOR is refused" (exit 0: the commit landed
under the name `Claude ()`), and "a refusal commits nothing". So `set -u`
alone would not cover the empty case.
**Fixture.** The repo's identity comes from its own config, with
`GIT_CONFIG_GLOBAL=/dev/null` and `GIT_CONFIG_NOSYSTEM=1`, and every `GIT_*`
identity variable is unset first. So a caller's own identity cannot make the
author or committer assertions agree by accident.