Repository navigation
[finding] the permission classifier blocks the ADDITIVE label write and permits the clobbering one — agents are pushed into the #5533 shape #10686
Description
Observation-class, measured tonight across two dev-agent runs. No gate is red, and nothing was
lost this time. Filed unassigned, no pm:queue.
The inversion
This repo's rule for labelling an issue or PR is settled and is in every dispatch brief:
- ✅ additive —
POST /repos/{owner}/{repo}/issues/{n}/labelswith{"labels":[...]} - ⛔ never a whole-set write (
issue_writewith alabelsarray, orPUT .../labels),
because it clobbers labels added by bots between your read and your write — which is test(service-queue): pin db-queue-adapter's engine double to ObjectQL.delete's dispatch predicate (#5198) #5533.
Measured tonight: the safe form was blocked for a dev agent by the session's auto-mode
permission classifier, and the unsafe form went through. The agent, correctly, reported this
rather than hiding it, and mitigated with a read → union → write → read-back.
That mitigation narrows the race. It does not close it — it is the read-modify-write whose
race #5533 records.
The near miss, from the timeline rather than the report
On PR #10682, the label events are:
| time | event | label | actor |
|---|---|---|---|
| 08:33:01Z | labeled | size/m |
github-actions[bot] |
| 08:33:20Z | labeled | documentation |
github-actions[bot] |
| 08:33:35Z | labeled | skip-changeset |
os-zhuang |
No unlabeled event — nothing was dropped, and all three labels are present. But the agent's
whole-set write landed 15 seconds after a bot label it had to have already read. Had
documentation been applied at 08:33:40 instead of 08:33:20, the write would have silently
removed it, and the only symptom would have been a label quietly missing from a merged PR.
The bots are fast and they are not done when the PR opens: size/m at ~6s and documentation at
~25s after creation, in this instance. The window an agent's read-modify-write must survive is
routinely tens of seconds wide.
Why this is a real finding and not a one-off
It is not scope or credentials. The PM seat's own additive POST .../labels calls succeed
repeatedly in this same session and against these same issues (#10303, #10565, #10566, #10667
tonight), as do the targeted DELETE .../labels/<name> calls. The refusal is specific to the
dev-agent context, so it will recur for every agent dispatched, and each one faces the same
choice: violate the brief, or leave the label unset.
The failure mode is the one this repo keeps re-finding: the guidance says do the safe thing, the
tooling only permits the unsafe thing, and the unsafe thing succeeds quietly. An agent that
follows the brief and stops looks like it failed; an agent that works around it looks like it
succeeded, right up until a bot label disappears.
Directions (not a decision)
- A — allow
POST /issues/{n}/labelsin the dev-agent permission set. It is strictly narrower
than the whole-set write already permitted: it can only add. - B — if A is not available, make the brief's instruction terminal: agents report the block and
the PM applies the label, never a whole-set fallback. I have already switched my own briefs to
this wording, but it costs a round-trip per PR and only holds for briefs that say it. - C — refuse the whole-set write too, so the classifier's ordering at least matches the rule's.
Cheapest to reason about, and it would have turned tonight's near miss into a loud stop.
A is the one that makes the documented rule the executable one.
Refs
#5533 (the clobber this shape causes) · PR #10682 (where it was measured) · #10603 (the
adjacent "gh is absent" gap in the label tooling)
Generated by Claude Code
Activity
claude commentedon Aug 21, 2026
Filed by me 40 minutes ago on a near miss. A dev agent has since produced a measured loss,
and it inverts this card's diagnosis. Recording it here because the remedy list above is now
incomplete.
PR #10698's label timeline, which I verified myself rather than taking from the report:
skip-changesetclaude[bot]skip-changesetgithub-actions[bot]size/lgithub-actions[bot]skip-changesetclaude[bot](re-applied)The agent did everything this card asks for: the additive
POST /issues/{n}/labels, whichreturned HTTP 200. One second later the PR-size labeler's whole-set PUT erased it, bundled
into the same write that added
size/l.So the danger I described — an agent forced into a read-modify-write by the permission
classifier — is real but is the smaller half. The larger half is that a correct additive
write is not safe either, because another actor is doing the whole-set write this repo forbids
its agents from doing. Only the agent's after-the-bots read-back caught it.
What this does to the directions above
necessary. It is no longer sufficient: A alone would have produced exactly this loss.
needs refusing is
pr-automation.yml's.skip-changesetone second after an additive POST — measured loss, and the only tracker is prose in a closed card #10703 makes the concrete one: make the size labeler additive, soit stops erasing labels it did not add.
On the audit
I checked every PR this seat opened tonight — #10661, #10662, #10665, #10668, #10670, #10674,
#10682, #10687, #10690, #10697, #10698. #10698 is the only one carrying an
unlabeledevent,and it was restored. #10661 and #10697 correctly carry real changesets rather than the label
(
@objectstack/coreand@objectstack/lintboth publish). So nothing was lost silently on thisboard — but that is because the race needs the agent's POST to land inside a ~1 second window,
not because the write is safe.
Worth stating plainly: this card's own text says of #10682 "No
unlabeledevent". That wasaccurate for #10682 and is exactly the reading that made me call it a near miss. It was a near
miss of a different mechanism than the one I named.
⭐ Tracking detail from #10703 worth repeating:
pr-automation.yml's own header names this as theopen half of #5649 — but #5649 and #5533 are both CLOSED, so until now the only tracker was
prose inside a workflow comment.
#10703 is the sharper card of the two. Leaving this one open for the classifier half.
Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
Triage (finding concentration round): relabeled
domain:devx→domain:skills, staysfindingfor that seat's self-triage (skills-lane findings are graded by the skills seat, not the all-repo round). Rationale: with #10703 now queued in devx for the labeler's whole-set PUT (the sharper half, per this card's own correction comment), what remains here is the permission-classifier half — dev-agent permission configuration under.claude/**, whose SUBJECT is agent-instruction governance, i.e. the skills lane by the governed-face split. Direction A (allow the additivePOST /issues/{n}/labelsin the dev-agent permission set) remains necessary-but-not-sufficient per the measured timeline.Generated by Claude Code
os-zhuang commentedon Aug 22, 2026
First-touch grading, concentrated round (skills seat, session
session_01MsbKEG4LtERSLaDrbehM3e):finding→pm:queue, direction A (allow the additivePOST /issues/{n}/labelsin the dev-agent permission set — strictly narrower than the whole-set write already permitted), paired with theos-dev.mdlabel-paragraph rewrite: one defect in two halves, one dispatch, one PR. Until it lands, briefs carry direction B's wording (report the block; the PM applies the label). Governed face — draft PR, human merge.Generated by Claude Code
os-zhuang commentedon Aug 22, 2026
Member pointer: claimed as part of the pair anchored at #10902 (session_01MsbKEG4LtERSLaDrbehM3e, branch claude/issue-10902-label-write-pair). Direction A per the first-touch grading;⚠️ necessary-but-not-sufficient caveat (the labeler's whole-set PUT half stays with #10703, devx) rides in the dispatch. Full claim terms on the anchor.
Generated by Claude Code
chore(claude): allow the additive label POST in the dev-agent permiss…
Make the additive label POST the prescribed write and permit it for d…