Skip to content

[finding] os-dev.md tells every agent the label write IS a whole-set PUT — the additive POST endpoint exists and was measured working from an agent seat #10902

Closed

Description

@claude

What

.claude/agents/os-dev.md:272-273 tells every dispatched dev agent that the label write is a whole-set PUT, and prescribes read → union → write as the way to survive it:

打标签是你的步骤、不是 CI 的,PR 一建立就打;先读回、再写并集——标签写入是整组 PUT(裸集合会抹掉机器人刚打的标签,CI 的写入也可能抹掉你的;changeset 门的首轮可能与你的写入竞态)

The premise is false. GitHub's REST API has an additive endpoint that adds labels without touching the ones already there:

POST /repos/{owner}/{repo}/issues/{n}/labels   -d '{"labels":["skip-changeset"]}'

There is no union to compute, no read-back race to lose, and nothing a bot applied a second earlier can be erased by it.

Measured today, from an agent seat, not from the PM seat

An os-dev agent ran two authorized read-only probes with Authorization: Bearer $GITHUB_TOKEN:

request status
GET https://api.github.com/rate_limit 200
GET https://api.github.com/repos/objectstack-ai/objectstack/issues/10896/labels 200

The second is the exact read half of the additive flow, against the same endpoint the write uses, differing only in verb. So curl reaches api.github.com with working credentials from inside an os-dev seat. (gh is genuinely absent everywhere in this container — command -v gh exits 1 — which is a separate fact and not the reason.)

Two independent occurrences in one session

Both agents did exactly what os-dev.md tells them to, and both flagged it against the PM's brief:

  1. [finding] check:type-check-coverage has no invariant for a tsconfig include that names a GENERATED directory the package's typecheck script never generates #10880 / PR feat(gate): GENERATED_COVERED — a tsconfig include rooted in a generated path must be produced by the typecheck script, or declared as deliberately not produced #10896 — used mcp__github__issue_write with a labels array. That tool's array is the whole-set PUT. It reported the whole-set write as the only mechanism available; the two 200s above later showed that claim was an untested inference, which the agent withdrew.
  2. [finding] docs-audit: the ledger row recognizer now reads codeOnly but still not typeDeclRegions — a literal-union route: TYPE member still mints a silent phantom row #10793 / PR fix(devx): read the ledger row recognizer through typeDeclRegions too #10900 — read ['documentation','size/m'], wrote the union ['documentation','size/m','skip-changeset'], read back and confirmed both bot labels survived. Its words: "no additive-label tool in its MCP set, so I used the standing fallback in my own instructions."

Neither lost a label — the timelines for #10896 and #10900 both show zero unlabeled events. The procedure works. It is just doing by hand, with a race, what one call does atomically.

⭐ The part that makes this worth a card rather than a note

The PM seat has been writing an unconditional "never write labels as a whole set" into every dispatch brief, which directly contradicts the document those agents are bound by. An agent obeying os-dev.md violates the brief every time; an agent obeying the brief has to work out on its own that curl is available, because neither document names a mechanism. Two agents in one session hit that gap and resolved it differently, and both had to spend a paragraph explaining themselves.

That is the PM's error as much as the file's, and it is stated here rather than quietly fixed in future briefs so the contradiction is on the record.

Why the workaround is not merely redundant

os-dev.md's own parenthetical names the cost it cannot avoid:

CI 的写入也可能抹掉你的;changeset 门的首轮可能与你的写入竞态

Read → union → write loses to any writer that lands between the read and the write. The additive POST has no such window. The doc then says the step is closed by the read-back, not the write — which is a way of detecting the race after the fact, not of avoiding it. So this is not style: the sanctioned procedure is strictly less safe than the call it stands in for, for the exact failure mode #5533 is about.

Suggested shape — ⛔ not a PR from this seat

.claude/agents/os-dev.md is governed, human-merge-only. Filing rather than editing.

What the paragraph would need:

  1. State that the additive POST /issues/{n}/labels is the mechanism, and name it concretely enough to be executable (the curl line above), since "apply it additively" alone was demonstrably not actionable from an agent seat.
  2. Keep read-back as the closing step — that part is right and both agents used it well.
  3. Keep the whole-set read → union → write as an explicitly declared fallback for a seat where the additive call is refused, with the requirement to report the read-back. It should not be the default, and it should not be described as what the write "is".
  4. Leave the objectui half untouched — that the skip-changeset label does not exist there, and must never be created, is unrelated and correct.

Filed unassigned by the domain:devx PM seat (#6023). Observation class: the mechanism, its measurement, and the contradiction it produced.

Refs: #5533 (the whole-set PUT erasing bot labels) · #10703 / PR #10777 (the same verb in pr-automation.yml) · #10778 (nothing gates the verb) · #10880 / PR #10896 and #10793 / PR #10900 (today's two occurrences)


Generated by Claude Code

Activity

  1. huangyiirene commented on Aug 21, 2026

    @huangyiirene
    Collaborator

    Triage: re-routed domain:devx → domain:skills. Anchoring rule: the fix lands in .claude/agents/os-dev.md (agent instruction architecture, governed surface, human-merge-only) plus the pm-dispatch brief-writing discipline — both are the skills lane's territory per the 2026-08-18 ruling. Paragraph verified present on origin/main @ 58563be0 (os-dev.md:273, 整组 PUT). The finding label stays for the skills seat's own first-touch (standing exception: that seat self-triages its findings). Note for the skills seat: the card documents a live contradiction between os-dev.md's prescribed procedure and the dispatch briefs' standing "never whole-set PUT" clause — two agents hit it in one session; adjacent cards #10778 (nothing gates the verb) and #10703 (the PUT's measured erasure) are the same family's enforcement half.


    Generated by Claude Code

  2. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    First-touch grading, concentrated round (skills seat, session session_01MsbKEG4LtERSLaDrbehM3e): finding → pm:queue, paired with the permission-classifier half — one defect in two halves (the doc prescribes read→union→write as what the label write "is"; the classifier blocks the additive POST that makes the safe form executable). One dispatch, one PR: rewrite the os-dev.md label paragraph per this card's four-point shape (additive POST named executably; read-back kept; whole-set union demoted to a declared fallback; objectui half untouched) + the classifier change from the paired card. Governed face — draft PR, human merge.


    Generated by Claude Code

  3. self-assigned this
    on Aug 22, 2026
  4. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    Claim: session_01MsbKEG4LtERSLaDrbehM3e branch=claude/issue-10902-label-write-pair

    skills 车道执行席认领——对子派发锚点(成员:本卡 + #10686,一缺陷两半:os-dev.md 把标签写死写成整组 PUT 教程 / 权限分类器挡加法 POST 放整组写)。一次派发一个 PR,一员一提交各带 Fixes。定级评论(2026-08-22 15:34/15:35)即裁定依据:本卡四点形状(加法 POST 可执行地写明;read-back 保留收尾;整组并集降为申报式回退;objectui 半边不动)+ #10686 方向 A(dev-agent 权限集放行加法 POST /issues/{n}/labels)。⚠️ 必要非充分警示进文案:#10686 更正评论实测 labeler 的整组 PUT 可在 1 秒内抹掉正确的加法写(#10703 devx 车道处理那半),故 read-back 在 bots 之后仍是必须。治理面(.claude/**):draft PR + 人工合并。os-dev.md 为 fable 强制档,按此派发。


    Generated by Claude Code

  5. added a commit that references this issue on Aug 22, 2026
    8c0a24c
  6. os-zhuang commented on Aug 22, 2026

    @os-zhuang
    Contributor

    os-dev-report

    {
      "issue": 10902,
      "family": [10902, 10686],
      "status": "done",
      "branch": "claude/issue-10902-label-write-pair",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/11112",
      "premise_still_valid": true,
      "premise_per_member": {
        "10902": "valid — the read-union-write paragraph verified present at .claude/agents/os-dev.md:270-277 on origin/main (merge base f0d7647e8), teaching the whole-set PUT as what the label write is.",
        "10686": "valid with a measured nuance — the classifier's block of the additive POST did NOT reproduce from this seat this session (the same curl POST to this PR's labels endpoint returned HTTP 200 with no allow rule loaded), while the same classifier refused a chained spelling of an ordinary git push minutes earlier. Verdicts are shape- and session-nondeterministic, which strengthens rather than weakens direction A: an explicit allow entry is the only deterministic channel. Filed the nondeterminism evidence as #11114."
      },
      "line_budget": { "file": ".claude/agents/os-dev.md", "before": 392, "after": 395, "ceiling": 399, "net": "+3, paid from existing headroom 7 -> 4; no ceiling raise" },
      "commits": [
        { "member": 10902, "sha": "8c0a24c51", "files": [".claude/agents/os-dev.md"], "fixes": "Fixes #10902" },
        { "member": 10686, "sha": "f6d61c9b7", "files": [".claude/settings.json"], "fixes": "Fixes #10686" }
      ],
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs with NO hand-fed paths, run after the final commit (head f6d61c9b7): 2 committed paths vs merge base f0d7647e8, 8 families derived, all run, all green",
        "verdict_lines": [
          "check:pm-skill-ratchet: '✓ check-skill-line-ratchet: .claude/agents/os-dev.md is 395 lines (ceiling 399; headroom 4).'",
          "check:pm-skill-id-lint: '✓ check-skill-id-lint: 17 file(s) clean (pattern /#[0-9]{3,}/g).'",
          "check:doc-authoring: '✓ doc authoring guard: 389 files clean — no bare metadata literals.'",
          "check:nul-bytes: 'check-nul-bytes: OK (scanned 6426 text file(s) -- 6426 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'",
          "check:pm-governed-merges: '✓ check-governed-merges --self-test: 119 assertions ...'",
          "check:agent-model-declared: '✓ check-agent-model-declared: 1 agent definition(s) under .claude/agents/ all declare a model'",
          "check:skill-frame-sync: '✓ check-skill-frame-sync: 4 copies of the decision frame are structurally isomorphic across 3 files'",
          "check:doc-formula-expressions: '✓ check:doc-formula-expressions: 22 record-scoped formula example(s) across 416 files / 1447 TS blocks judged clean by @objectstack/formula.'"
        ],
        "exit_capture": "every exit captured via redirect-then-capture (cmd redirected to file; EXIT=$? read before any tail), never through a pipe"
      },
      "tests": "No package code touched (.claude-only diff), so the 8 derived gate families at f6d61c9b7 are the verification surface. The documented mechanism was additionally live-tested on the PR itself from this dev seat: additive POST to issues/11112/labels returned HTTP 200; read-back AFTER the size labeler settled shows both size/s (bot) and skip-changeset intact — zero unlabeled events, the additive write survived. PR assigned to os-zhuang via additive POST issues/11112/assignees, HTTP 201.",
      "deviations": [
        "settings.json is strict JSON and cannot carry a comment at the entry; the per-entry rationale lives in the PR body and in the os-dev.md paragraph the entries serve (the doc pins the exact curl spelling the allow rules match and says not to rewrite it).",
        "Mid-pattern wildcard support (the issue-number star in issues/*/labels) in Bash allow rules is unverified from this seat — settings load at session start, so the new rule cannot be exercised in this session. Failure direction if unsupported: the rule is inert and the classifier keeps deciding, i.e. exactly today's behavior, nothing wider.",
        "skip-changeset applied via the additive POST (the mechanism under documentation) rather than read-modify-write union; read-back performed after the labeler settled, quoted above."
      ],
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #11114: the auto-mode classifier's verdict varies with command chaining, not capability — a bare git push passed seconds after the chained spelling of the same push was refused (dedup-searched first; finding label, unassigned, no pm:queue)"
      ],
      "summary": "One PR, one commit per member. Member 1 (8c0a24c51): rewrote the os-dev.md skip-changeset paragraph to the adjudicated four-point shape — the additive POST named executably with the exact curl + Bearer line, the necessary-but-not-sufficient caveat folded in (a bot whole-set PUT measured erasing a correct additive write within about 1 second, so read-back happens after the bots settle and a vanished label is re-applied, not read as your error), the whole-set union demoted to a declared fallback that must report its read-back, objectui half byte-untouched. Member 2 (f6d61c9b7): two permissions.allow entries pre-approving exactly the canonical additive-POST spelling for objectstack and the objectui twin — strictly narrower than the whole-set write already permitted; repo-addressable via the same settings.json mechanism the repo already uses for agent-browser. Governed face: draft PR #11112, human merge only, assigned to os-zhuang."
    }

    Generated by Claude Code

  7. added a commit that references this issue on Aug 23, 2026
    f0e7d64
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions