Skip to content

Commit 5248328

Browse files
johanzanderclaude
andauthored
fix: let the Product Owner fire Stage 2, so its decisions carry its own face (#645)
`backlog`'s Autonomous spend rule authorises the PO agent to fire Stage 2 on an item entering Analysis that is labelled `bug`, opened by an external reporter, carries a debug log, and has no prior analyze comment. `issue-analyze.yml` gated on `comment.user.login == github.repository_owner` and nothing else. So the rule and the gate disagreed, and the only way to satisfy both was to post the trigger as the maintainer. That put the maintainers name on comments they never wrote -- #624, #627 and #643 all show `@claude-bot analyze` from `johanzander` -- and erased the distinction between what the maintainer decided and what the agent decided. Every other PO write (labels, board fields, reporter replies) already goes out as `bess-product-owner`; this was the one hole, and it was the one that mattered most, because it is the write that SPENDS. The gate now accepts `bess-product-owner` alongside the owner, and the skill tells the PO to use `gh-agent.sh --as po` rather than plain `gh`. STAGES 1, 3 AND 5 STAY OWNER-ONLY and the comment says they must not copy this. Their spend ($1-4 and $2-10) is authorised by no autonomous rule, so nothing would be enforcing a bar on the far side of the gate. The exception here is safe precisely because the rule that bounds it already exists. Naming an account in a gate is only safe when it is REGISTERED. `bess-product-owner` exists today and is a collaborator. This is the same reason CLAUDE.md refuses to pre-authorise `bess-developer` before the rename: on a public repo an unregistered username can be claimed by anyone. Claude-Session: https://claude.ai/code/session_012LExo6fcbup75vtc9NfoAR Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e924617 commit 5248328

3 files changed

Lines changed: 58 additions & 11 deletions

File tree

.claude/skills/backlog/SKILL.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,20 @@ Serialise, do not stack:
492492
## Autonomous spend
493493

494494
Exactly one action costs money without asking: firing Stage 2
495-
(`@claude-bot analyze`, ~$0.50–2) on an item entering Analysis that meets the
495+
(`@claude-bot analyze`, ~$0.50–2). **Post it as the PO, never as the
496+
maintainer:**
497+
498+
scripts/gh-agent.sh --as po issue comment <n> --body "@claude-bot analyze"
499+
500+
`issue-analyze.yml`'s actor gate accepts `bess-product-owner` for exactly this
501+
trigger. It did not always, and the mismatch had a real cost: the rule
502+
authorised the PO to spend while the gate accepted only the repo owner, so the
503+
trigger went out as the maintainer — putting their name on comments they never
504+
wrote, and hiding which decisions were the agent's. An automation decision
505+
carries the automation's face. If this ever fails the gate, **that is the
506+
finding** — report it; do not route around it with plain `gh`.
507+
508+
It fires on an item entering Analysis that meets the
496509
tier-1 bar from `Verb: next` directly — labelled `bug`, opened by someone
497510
other than the maintainer, with its debug log attached — **and that has no
498511
prior `@claude-bot analyze` comment already on the issue**. Check this by

.github/workflows/issue-analyze.yml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,27 @@ on:
1515
jobs:
1616
analyze:
1717
name: Deep Analysis
18+
# The Product Owner agent can fire this, not only the repo owner.
19+
#
20+
# `backlog`'s Autonomous spend rule already authorises exactly this: Stage 2
21+
# on an item entering Analysis that is labelled `bug`, opened by someone
22+
# other than the maintainer, carries a debug log, and has no prior analyze
23+
# comment. The rule existed while this gate did not accept the account that
24+
# enforces it, so the only way to obey both was to post the trigger as the
25+
# maintainer -- which put the maintainers name on comments they never wrote
26+
# and hid which decisions were the agents.
27+
#
28+
# Stages 3 and 5 stay OWNER-ONLY and must not copy this. Their spend
29+
# ($1-4 and $2-10) is not authorised by any autonomous rule, so nothing
30+
# would be enforcing the bar on the other side of the gate.
31+
#
32+
# Safe to name here because `bess-product-owner` is a REGISTERED account
33+
# and a collaborator on this repo. Never pre-authorise a username that does
34+
# not exist yet: on a public repo anyone could claim it (see CLAUDE.md on
35+
# `bess-developer`).
1836
if: |
19-
github.event.comment.user.login == github.repository_owner &&
37+
(github.event.comment.user.login == github.repository_owner ||
38+
github.event.comment.user.login == 'bess-product-owner') &&
2039
github.event.issue.pull_request == null &&
2140
contains(github.event.comment.body, '@claude-bot analyze')
2241
runs-on: ubuntu-latest

CLAUDE.md

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,30 @@ of `analyzed`.
124124

125125
### General bot rules
126126

127-
- Only the repo owner can trigger bot commands. The one exception is Stage 4:
128-
`pr-review.yml` also accepts `@claude-bot` from the developer automation
129-
identity (currently the `bess-agent` GitHub account; being renamed to
130-
`bess-developer` — see `scripts/gh-agent.sh`), so `implement-issue`'s
131-
Step 11 loop can request its own review. `bess-developer` is added to that
132-
gate only in the same commit that renames the account — never before,
133-
since pre-authorising an unregistered username on a public repo is
134-
exploitable. Stages 1–3 and 5 stay owner-only — those spend money on work
135-
nobody has asked for yet.
127+
- Bot commands are owner-triggered, with **two** exceptions, each matching an
128+
account that a documented rule already makes responsible for that spend:
129+
- **Stage 4**`pr-review.yml` accepts `@claude-bot` from the developer
130+
identity (currently `bess-agent`; being renamed to `bess-developer` — see
131+
`scripts/gh-agent.sh`), so `implement-issue`'s Step 11 loop can request its
132+
own review.
133+
- **Stage 2**`issue-analyze.yml` accepts `@claude-bot analyze` from
134+
`bess-product-owner`, because `backlog`'s Autonomous spend rule authorises
135+
exactly that trigger and bounds it (labelled `bug`, external reporter,
136+
debug log attached, no prior analyze). The rule existed before the gate
137+
accepted the account enforcing it, so the only way to satisfy both was to
138+
post as the maintainer — putting their name on comments they never wrote
139+
and hiding which decisions were the agent's. An automation decision should
140+
carry the automation's face.
141+
142+
**Stages 1, 3 and 5 stay owner-only, and must not copy this.** Their spend
143+
($1–4 and $2–10) is authorised by no autonomous rule, so nothing would be
144+
enforcing a bar on the other side of the gate.
145+
146+
Only ever name a **registered** account in a gate. `bess-developer` is added
147+
in the same commit that renames the account, never before: on a public repo
148+
anyone can claim an unregistered username, so pre-authorising one is
149+
exploitable. `bess-product-owner` is named above because it exists today and
150+
is a collaborator.
136151
- Automation writes carry a **role** identity, and role is the axis:
137152
`bess-product-owner` (intake, backlog, board, reporter comments),
138153
`bess-developer` (analyze, fix, PR authorship, requesting review),

0 commit comments

Comments
 (0)