Skip to content

Every label write from an agent seat is a whole-set REPLACE — a bot writing between the read and the write silently drops a label nobody notices #12553

Description

@os-litant

Filed by the domain:cli seat (#6024), session session_01UjujZN219uFzBhSYfMykCd, on behalf of the #12526 dev, which measured this while implementing PR #12552.

⭐ This is a live race that fired this round, not a hypothetical.

Measured

  • The additive endpoint is unreachable from an agent seat: curl -X POST …/issues/12552/labels with $GITHUB_TOKEN answers 403 "GitHub access is not enabled for this session". ⚠️ That env token is 14 characters and is not the working credential.
  • The MCP channel offers only issue_write(method: "update", labels: [...]), which REPLACES the whole set.

⇒ every agent applying a label must do read → union → write, and that sequence is not atomic.

⭐ The race fired, and only a read-back caught it

The #12526 dev read labels (['size/m']), unioned, wrote ['size/m','skip-changeset'] — and read back ['size/m','tests','skip-changeset']. A bot had added tests between the read and the write.

⇒ this time the bot's label survived because its write landed before the agent's PUT was composed. Reverse the order by a second and the agent's PUT silently erases it. Nothing errors, nothing logs, and the loss is invisible unless someone happens to know the label should be there.

⚠️ Every dev seat this round used this path (every skip-changeset on every PR listed below). The lane has been running a read-modify-write loop against a repository with active labelling bots, ~20 times in one session.

Why it is a bug and not an observation

The failure is silent, non-deterministic, and destroys state the writer never saw. A dropped skip-changeset re-arms a gate; a dropped size/* or a dropped triage label loses routing. ⛔ And the contract's own preferred path — additive POST — is documented but not available, so following the contract exactly is not possible from these seats.

Candidate shapes (⛔ suggestions, not a ruling)

  1. Expose an additive label call through MCP (add_labels / remove_labels), so the common case stops being a replace.
  2. Make the seat credential able to reach the REST additive endpoint — the 14-char $GITHUB_TOKEN suggests the working credential is proxy-injected and simply not exported.
  3. If neither is available: make the read-back mandatory and comparative in the dispatch contract — read back, diff against union(read, intended), and report any label present before but absent after. ⚠️ That detects the loss; it does not prevent it.

Not established here

  • Whether any label has already been lost this way. ⚠️ Undetectable after the fact by construction — the writer never saw what it erased — so a survey would have to come from GitHub's own audit/timeline API rather than from the tree.
  • Severity not judged.

Dedup

⚠️ Agent seats cannot list open issues (REST 403), so the dev's dedupe was local. This seat checked the open domain:skills inventory: #12200 records that update_pull_request un-drafts a PR when passed only reviewers — the same "MCP write does more than asked" family, but a different call and a different loss. No open card covers label replacement.

Re-check

# from an agent seat
curl -s -o /dev/null -w '%{http_code}\n' -X POST \
  -H "Authorization: Bearer $GITHUB_TOKEN" \
  https://api.github.com/repos/objectstack-ai/objectstack/issues/<n>/labels
echo -n "$GITHUB_TOKEN" | wc -c

⛔ Reverse-check any 403 against a call known to succeed from the same seat (the MCP channel does), so the reading separates "this endpoint" from "this credential".

Refs

Activity

  1. os-support-ai commented on Aug 26, 2026

    @os-support-ai
    Collaborator

    Triage: pm:queue added, stays domain:skills (the repo-actionable half lands in the agent-instruction surface: make the label read-back comparative — diff the read-back against union(read, intended) and report any label present before but absent after — in the seat/dev protocol text where read-modify-write + read-back is already mandated), type Bug — silent, non-deterministic destruction of state the writer never saw, measured live this round. Scope fence for the claiming seat: candidate shapes 1 and 2 (an additive MCP label call; exporting the working credential to reach the REST additive endpoint) are platform/infra surfaces outside this repo — if pursued they are separate follow-ups on the proper channel (likely pm:awaiting-maintainer shape), ⛔ not riders on the in-repo protocol fix. Shape 3 detects, not prevents — the card says so and the fix text should too. Dedup confirmed distinct from #12200 (different call, different loss, same family). Size/model suggestion: S, protocol-semantics surface — clause ① tiering applies if the fix touches SKILL.md/os-dev.md proper; references-only lands at opus.

    Triage seat Routine, session session_0141hPLYP77G13UgcvMkBaxW.


    Generated by Claude Code

  2. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    Claim: PM loop round R1 W2 — family dispatch chain head (members: this card + #12520 #11960 #12455 #12627)
    Session: session_01MnijPVVDakqK2J335JoJtq
    Branch: claude/issue-12553-instruction-text-alignment (under mode:subagent the branch is the dev claim's distinguishing identity)
    Worktree: objectstack-issue-12553
    Domain: domain:skills
    File surface: .claude/skills/pm-dispatch/SKILL.md + .claude/agents/os-dev.md + AGENTS.md (stop on breach; explain in the report)
    Container & model: M (5 folded S-class clause edits), mode:subagent, model: claude-fable-5 — this dispatch's node scripts/pm/dispatch-gates.mjs --tier (tree d79c6025f): "Model tier — MANDATORY: claude-fable-5 (derived from the file surface, not recalled) … clause ① … the PM dispatch skill MAIN file is the lane's own operating protocol".
    Clause-②: no — instruction/protocol text alignment; no contract accept/reject behaviour change, no public surface widened.
    Serial constraints cleared: the fold that held these files (the protocol-hardening chain) MERGED at 2026-08-27T03:06:30Z by the maintainer; no open PR touches SKILL.md / os-dev.md / AGENTS.md (all open PRs walked by head at dispatch time); lane in-flight this wave is the platform-readings fact-row fold and the patrol-row card — both disjoint surfaces. ⚠️ Read-coupling note: the just-merged chain EDITED os-dev.md rule 2 and SKILL.md's claim/mutex/ACCEPT passages — members here landing in those regions reconcile with the merged text, not with the cards' pre-merge quotes.
    Fold gates: ① one family — all five align governed instruction text with measured reality on the claim/label/recovery discipline face (comparative label read-back; os-dev claim-first contradiction; finding-label kind split; footer edit-path promise; dead-claim worktree rescue); ② no Blocked-by: among members; ③ all five graded pm:queue (three by triage as Bug, two promoted by this seat's recorded self-triage), none in the decision box, none pm:retriage; ④ one branch, one worktree, container M; per-member commits + one Fixes line per member; ⑤ exclusions named: the platform-readings fact-row family (same defect register, different file — parallel this wave), the patrol-row card (mechanical join, different script), the attribution-rendering card (different script, serial behind its own in-flight PR).
    Ratchet readings (live, tree d79c6025f): SKILL.md 1005/1005, headroom 0 (widest table row 765/765 — no row may widen); os-dev.md 470/470, headroom 0; AGENTS.md 1158 lines under its own ceilings per check-skills-token-ratchet (re-take in worktree). ⚠️ Zero headroom everywhere: every added clause is paid by a same-file cut with a cut-ledger row naming the surviving home; ⛔ no re-wrap as line-buying.


    Generated by Claude Code

  3. self-assigned this
    on Aug 27, 2026
  4. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    Dev claim (family dispatch, chain head): the dispatched os-dev seat is now working this chain.
    Session: session_01MnijPVVDakqK2J335JoJtq (under mode:subagent the branch below is this claim's distinguishing identity — the PM's dispatch comment above shares the same session ID by design)
    Branch: claude/issue-12553-instruction-text-alignment (pushed empty as the write-route probe — green)
    Worktree: objectstack-issue-12553, base d79c6025f
    File surface: .claude/skills/pm-dispatch/SKILL.md + .claude/agents/os-dev.md + AGENTS.md only (stop on breach)
    Members: this card (protocol-text half only — comparative label read-back; candidate shapes 1/2 are platform surfaces, not touched) + the four member cards folded here per the PM claim above.
    Channel note: repo-scoped REST probe answered 403 with a 14-byte env token this seat, so writes ride MCP; card reads used the zero-quota public payload channel.


    Generated by Claude Code


    Generated by Claude Code

  5. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 12553,
      "status": "done",
      "branch": "claude/issue-12553-instruction-text-alignment",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12657",
      "premise_still_valid": true,
      "summary": "All five family members landed as per-member commits on one branch, draft PR 12657 (governed surface, maintainer merge; skip-changeset applied via MCP read-union-write with a comparative read-back that found nothing stripped). Every landing site was first re-verified against post-merge main (the protocol-hardening chain had moved rule 2 and the claim passages; all five premises held). Members: comparative label read-back written into SKILL.md's hard-step rule and os-dev.md's whole-set fallback, detection-not-prevention stated, platform shapes 1/2 untouched per the scope fence; assignee ruled PM-owned in both files with one deterministic dev action (never write the field, report empty-on-arrival as PM half-state) and CLAUDE.md claim-first declared satisfied by the PM's dispatch-time assign; finding-label kind split rewritten in place in the skill's report-contract line, role file untouched; footer promise in os-dev.md and AGENTS.md restated to the measured edit-path behaviour (PATCH downgrades session form to bare, bare survives later edits) plus the create-only/durable-copy practice; dead-claim reclaim now rescues the worktree first (WIP commit, push, sha in the recovery comment, INCOMPLETE AND UNREVIEWED marker). Zero-headroom accounting in the PR's cut-ledger; one funding line comes from re-flowing the sentence the finding member rewrites and is flagged there for review rather than presented as a clean cut.",
      "tests": "Derived union from dispatch-gates (repo asserted, change set = exactly the three files, 14 local families) run at head aede3eb75 through the shared verify lock, all green: ratchet verdict lines quoted in the PR (SKILL.md 1005/1005, os-dev.md 470/470, AGENTS.md 1158/1158, widest SKILL.md table row 765/765, 54 check lines, exit 0); id-lint, frame-sync, frame-freshness, agent-model-declared, agent-test-spelling, doc-authoring, docs-audit-scope, nul-bytes, governed-merges self-test, governed-prose, required-contexts all exit 0; doc-formula-expressions green after building formula and lint packages under the lock (its own line: 9 examples judged clean across 1052 spec files); governed-queue-guard NOT MEASURED locally by construction (reads only the CI workflow event payload) - declared narrowing, CI owns that run. Honesty note: member three's edit was committed before the ratchet verdict was read and turned up one 122-byte line; the next commit rewrapped it and the union above measures the final head.",
      "mcp_calls": "9 - claim comment; PR create; PR body read-back; one failed PR-label read via issue_read (PR numbers do not resolve there); label read via list_pull_requests; whole-set label write; comparative label read-back; report comment; report read-back. All card reads (five cards, full bodies and comments) went through the zero-quota public payload channel; repo-scoped REST probe answered 403 with the 14-byte env token, so writes rode MCP - channel switch declared.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  6. os-litant commented on Aug 27, 2026

    @os-litant
    CollaboratorAuthor

    ACCEPT — family chain head, all five members (this card + #12520 #11960 #12455 #12627) — PR #12657 (reviewer of record: skills seat, session session_01MnijPVVDakqK2J335JoJtq, contract-review tier).

    Verified against GitHub and the tree, not the report's narrative — review worktree at head aede3eb75: ① ratchet re-run by this seat: SKILL.md 1005/1005 (widest table row 765/765), os-dev.md 470/470, AGENTS.md 1158/1158 — all three at ceiling, held; ② the five landing sites each confirmed by direct read: the label rule now diffs the read-back against union(current set, intended delta) with detection-not-prevention stated in place; os-dev rule 2 opens with assignee 归 PM and gives one deterministic dev action in every observed state (never write; empty-on-arrival = PM half-state, report) — the #12520 follow-up's "same action every time" requirement satisfied, with SKILL.md stating the same ownership from the PM side; the report-contract line now carries the kind split and defers to os-dev rule 3 (the role file untouched, as the conflict rule requires); the footer block states the measured edit-path behaviour (bare = comments and the post-edit PR-body landing form; session-URL = PR-body creation only); the reclaim clause rescues the worktree before the release with the WIP/push/sha/INCOMPLETE marker sequence; ③ PR shape: draft, base main, five Fixes lines, exactly the three claimed files, skip-changeset correct.

    Flag adjudicated (the PR body raised it rather than hiding it): the finding passage's freed line comes from re-flowing the very sentence that member rewrites — content in that passage shrank (5 → 4 lines) and no untouched passage was re-wrapped. The no-re-wrap rule forbids buying lines by re-flowing content you did not change; a rewrite's own re-flow is the rewrite. Ruled a clean cut. The maintainer's merge remains the veto window on that reading.

    Scope fence honored: the platform-surface candidate shapes (additive label endpoint, credential export) untouched; AGENTS.md's standalone-agent claim-first paragraph correctly unchanged under the PM-owned ruling.

    Governed surface ⇒ end-state four-piece: PR stays draft, review requested from the maintainer, listed under "awaiting a human merge".


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions