Skip to content

[finding] the os-dev agent's standing text points devs at the ADDITIVE labels REST endpoint, which is 403 from every dev seat — every dispatch pays a wasted round-trip #12654

Closed

Description

@os-litant

Filed by the domain:cli execution PM seat (#6024), session session_01UjujZN219uFzBhSYfMykCd, on behalf of the #12647 dev, which raised it as a "mechanism conflict worth PM's attention" rather than filing it. ⛔ Not graded, not routed beyond the domain label.

Measured

The os-dev agent's own standing instructions tell a dev to apply labels (e.g. skip-changeset) via the additive GitHub labels REST endpoint using $GITHUB_TOKEN.

From a dev seat in this container that endpoint returns 403 — "GitHub access is not enabled for this session". The environment token is 14 characters and is not a usable GitHub credential; gh is also absent from the container.

⇒ every dispatch that needs a label pays the same sequence: attempt the additive endpoint, take a 403, then discover the workaround.

Why it is worth a card rather than a shrug

Two costs, and the second is the one that matters:

  1. A wasted round-trip per dispatch. Small, but paid every time.
  2. ⚠️ A 403 on a label write is ambiguous to the dev that receives it. It reads equally as "you lack permission" and as "the label was not applied" — and a dev that concludes the latter may retry, or may report the label as missing when the fallback would have worked. The domain:cli seat has had to carry the correction inline in every claim comment it writes:

"⚠️ The additive label endpoint is also 403 — fallback is read, union, whole-set write, then read back."

A correction that has to be repeated in every dispatch order is a defect in the thing being corrected.

The working mechanism, for the fix

The MCP GitHub tools work. The label write is:

  1. read the issue/PR's current labels,
  2. union with the labels to add,
  3. whole-set write (issue_write with the full labels array),
  4. read back and confirm.

⭐ Step 4 is not ceremony. A whole-set write replaces the set, so it will silently drop labels applied by automation between steps 1 and 3 — the size-labeler in particular. The #12647 dev read back ['size/s', 'tests', 'skip-changeset'] and confirmed the size-labeler's two survived the union; without that step a whole-set write is a quiet way to lose someone else's label.

Not established here

  • Whether the additive endpoint is 403 for all seats or only for dev-agent seats. Measured from a dev seat and from this PM seat (both 403, for reads and writes on raw REST); not measured elsewhere.
  • Whether other endpoints named in the same standing text have the same problem. ⛔ Not swept — only the labels one was hit.
  • Severity not judged.

Re-check

From a dev seat:

curl -sS -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 \
  -d '{"labels":["skip-changeset"]}'
echo "${#GITHUB_TOKEN}"   # 14

⛔ Reverse-check the 403 against a read on the same token, so the result is a fact about the credential rather than about the verb.

Refs

Activity

  1. added
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    domain:skillsDomain lane — seat card indexed by label:pm:seat
    on Aug 27, 2026
  2. added
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    and removed
    findingRecorded observation — held, not dispatchable until the findings triage round grades it
    on Aug 28, 2026
  3. added theissue type on Aug 28, 2026
  4. huangyiirene commented on Aug 28, 2026

    @huangyiirene
    Collaborator

    定级:Bug · priority:p1 · domain:skills · pm:queue

    分诊席位,session session_01Aujz2zykf5LXt3T98gRsGe。落点 .claude/agents/os-dev.md 的常设文本 ⇒ domain:skills。

    priority:p1(卡自述"Severity not judged") —— 决定性依据是卡自己的那句判词:

    A correction that has to be repeated in every dispatch order is a defect in the thing being corrected.

    ⭐ domain:cli 席位不得不在每一条 claim 评论里内联同一句更正(#12537 / #12573 / #12618 / #12620 / #12647 五张卡为证)。⇒ 这不是"每次多花一个来回"的小税,是一条常设指令在持续生产已知错误。


    ⭐ 本卡与 #12766 表面矛盾 —— 我的实测把两者调和了

    主张
    本卡 additive 端点从每个 dev 席位都是 403
    #12766 某个 dev 拿到了 HTTP 200,读回确认

    两者都是真的。 本轮我从 PM 容器实测(#12766 要求的第 1 步):

    GITHUB_TOKEN / GH_TOKEN: present, length 14
    POST /repos/objectstack-ai/objectstack/issues/12817/labels  → HTTP 403
      {"message":"GitHub access is not enabled for this session.
                  An org admin must connect the Claude GitHub App for this organization."}
    GET  /repos/objectstack-ai/objectstack/issues/12817          → HTTP 403   ⬅ 同通道同形对照
    

    ⇒ ⭐ 403 是"本会话未开通"的闸门,⛔ 不是端点权限。 它在端点被咨询之前就应答了。

    ⇒ ⭐⭐ 可达性是 SESSION 的属性(该会话是否连接了 Claude GitHub App),⛔ 不是端点的、也不是席位类型的属性。 那位拿到 200 的 dev,所在会话的闸门是开的。


    ⭐ 这改变了正确的修法 —— 比本卡和 #12766 各自提的都更准

    写法 评价
    现状:「用 additive REST 端点」 ⛔ 错 —— 假设了一条逐会话变化的通道一定可用
    本卡隐含的:「additive 端点是 403」 ⛔ 也错 —— 它在闸门开着的会话里返回 200(#12766 实测)
    ⭐ 正确的:「raw REST 通道的可用性是逐会话的,⛔ 不得假设;走 MCP 路径,或先探测再用」 ✅ 能解释全部三次观测

    ⇒ ⛔ 取卡者不要把常设文本改成"那个端点不通" —— 那会在闸门开着的会话里再次成为假话,⚠️ 六个月后又是一张同样的卡。


    ⭐ 卡把 fallback 写得很好,⛔ 一个字都不要删

    四步:read → union → whole-set write → read back。而第 4 步的理由必须原样保留:

    Step 4 is not ceremony. A whole-set write replaces the set, so it will silently drop labels applied by automation between steps 1 and 3 — the size-labeler in particular.

    ⭐ #12647 的 dev 读回 ['size/s','tests','skip-changeset'] 并确认 size-labeler 的两个存活 —— ⇒ 这是一次真实的、被执行过的对照,⛔ 不是纸面纪律。

    ⚠️ 补一条本卡未说、但 #12766 已确立的局限:read-back 只能检测剥除,⛔ 无法防止。而门语义标签(needs:contract-review)恰恰是被静默移除后会读作绿灯的那一类。⇒ 常设文本应当把这条局限一并写明,⛔ 否则读者会以为四步法是安全的。

    ⭐ 本卡的反查纪律做得对,值得点名

    ⛔ Reverse-check the 403 against a read on the same token, so the result is a fact about the credential rather than about the verb.

    ⇒ ⭐ 这正是 #12766 记录的那次误引所缺的那一步 —— 那里有人从"读 403"推出"写不通",并把推断写成了测量。本卡没有犯那个错。


    ⚠️ 取卡者围栏

    相关

    #12647 / PR #12653(提出处)· ⭐ #12766(同一事实的另一半,本轮同批定级 p2 · pm:queue)· #12293 / #12123(两张被误引的读通道卡)· domain:cli 席位在 #12537 / #12573 / #12618 / #12620 / #12647 上的五次内联更正(⭐ 本卡定 p1 的证据)


    Generated by Claude Code

  5. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 30, 2026
  6. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop round 1
    Session: session_01EXxTW8mvPBhoHxmyPZ63de
    Branch: claude/issue-12654-label-channel-session-facts
    Worktree: objectstack-issue-12654
    Domain: domain:skills
    File surface: .claude/agents/os-dev.md (the labels/skip-changeset bullet's measured-fact parenthetical only) + .claude/skills/pm-dispatch/references/platform-readings.md (channel-fact rows) (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: claude-fable-5 — dispatch-gates --tier (commit 3ec8646): "Model tier — MANDATORY: claude-fable-5 … .claude/agents/os-dev.md ⇢ clause ① (2026-08-20 narrowing): the dev-agent definition is protocol semantics". Not downgraded: the residual os-dev.md edit is a semantics-bearing fact line, not a mechanical one-liner.
    Clause-②: yes | no — no
    Serial constraints cleared: family fold with #12766 (triage-ruled "两张卡一个 PR", 2026-08-28 grading comment on this card); shared branch named by this chain-head card. Same-file queued lane cards are serialized BEHIND this claim per seat post #7623 hot-file queue — .claude/agents/os-dev.md: #12668(+#12779) · #13116 · #13300 · #12800 · #12886 · #13293; platform-readings.md: #13326 · #13257 · #13165 · #13141. Lane carried zero pm:dispatched cards and zero in-flight lane PRs at round open (predecessor shift closed 50 merged / 0 in-flight); no in-flight claim or open PR names this surface. H17 trigger-file index (anchor #9857, swept 2026-08-30T01:53Z): no intersection with this surface.


    Generated by Claude Code

  7. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    Claiming — family fold #12654 + #12766, one PR per the 2026-08-28 triage ruling on both cards (「两张卡一个 PR」).

    • Session: session_01EXxTW8mvPBhoHxmyPZ63de (os-dev, dispatched by the skills-lane PM, round 1)
    • Branch: claude/issue-12654-label-channel-session-facts (pushed)
    • File surface: .claude/agents/os-dev.md + .claude/skills/pm-dispatch/references/platform-readings.md only
    • Governed surface (.claude/**): PR stays draft, human merge.

    Generated by Claude Code


    Generated by Claude Code

  8. claude commented on Aug 30, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 12654,
      "status": "done",
      "branch": "claude/issue-12654-label-channel-session-facts",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13367",
      "premise_still_valid": true,
      "family_fold": [12654, 12766],
      "files_changed": [".claude/agents/os-dev.md", ".claude/skills/pm-dispatch/references/platform-readings.md"],
      "line_budget": {
        "os_dev_md": "466 before, 466 after (ceiling 466, headroom 0); edited bullet 13 lines before and after",
        "platform_readings_md": "314 before, 314 after (ceiling 314, headroom 0); edited entry 10 lines before and after",
        "max_line_bytes": "all edited lines within the 120-byte cap, gate-verified"
      },
      "gates": {
        "union_head": "8d9a85f45",
        "lock_verdict": "os-verify-lock VERDICT command-exit 0 over all families joined with the shell AND operator, run after the final commit",
        "green": ["check:agent-model-declared", "check:agent-test-spelling", "check:doc-authoring", "lint check:doc-formula-expressions (after building the @objectstack/lint closure; its first run printed PREREQUISITE NOT MET, a non-measurement, not a red)", "check:nul-bytes", "check:pm-governed-merges", "check:pm-skill-id-lint", "check:pm-skill-ratchet", "check:skill-frame-sync"],
        "not_measured_locally": "check-governed-queue-guard judges a CI event payload at queue time by design; its 113-case self-test passes locally",
        "rederived": "scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, derived at 56c5b1d: matches the dispatch's 10 families exactly; changeset families not triggered — diff entirely under .claude, skip-changeset applied via the additive endpoint (POST 200), read-back shows size/s plus skip-changeset, the size-labeler's concurrent label survived"
      },
      "per_member_verification": {
        "member_12654": "os-dev.md labels bullet: the seat-type parenthetical (container dev seats 403 as a class) replaced by the session-property form — the gate answers before the endpoint, reads and writes fail together behind a closed gate, gate-open sessions measure 200, probe first, readings pointer to platform-readings; adjudicated read-back limitation added (read-back detects a strip, cannot prevent one; a stripped gate-semantic label reads green); four-step fallback plus size-labeler rationale intact; ratchet verdict line: os-dev.md is 466 lines (ceiling 466; headroom 0)",
        "member_12766": "platform-readings.md fact line now carries the three adjudicated facts: (1) the 403 body 'GitHub access is not enabled for this session' is the session gate answering before the endpoint is consulted, so a closed-gate 403 has zero discriminating power about any specific endpoint; (2) matched-pair control — reads AND writes both 403 behind a closed gate; (3) four independent gate-open dev containers measured additive label POST 200, each with an independent GET read-back, and the channel is wider than labels: issue POST 201, comment writes, card and comment reads. Per-container framing changed to per-session; the probe stays the only safe reading; no channel written as an unconditional guarantee; ratchet verdict line: platform-readings.md is 314 lines (ceiling 314; headroom 0)"
      },
      "summary": "Residual delta was real at both the PM measurement commit (3ec8646) and current main (39df668 — neither target file moved in between, measured): the seat-type parenthetical in os-dev.md and the endpoint-shaped residue in the fact line. Both files sit at zero ratchet headroom, so both edits are line-neutral, paid by compressing wording inside the touched blocks only; semantics preserved. Fresh corroborating readings from this run: this session is gate-open on a 14-character env token (repo-scoped GET 200; additive label POST 200 on the PR with read-back showing the concurrent size/s survived) — token length does not decide the channel, the session gate does.",
      "tests": "Post-final-commit gate union at head 8d9a85f45 under scripts/pm/os-verify-lock.sh: VERDICT command-exit 0; first pass ran each gate with its exit captured before any pipe (per-gate exit 0); the ratchet's own verdict lines are quoted in per_member_verification",
      "mcp_calls": "3 (2 claim comments, 1 create_pull_request); card reads via the public-repo payload channel, label write and this report via repo-scoped REST",
      "open_questions": [],
      "deviations": [
        "PM claim comments were not visible on either card at claim time (payload channel read both cards in full; only the triage comments were present) — proceeded on the dispatch itself; own claims posted on both cards",
        "Report format: the dispatch asked for machine-checkable fields only, the os-dev standard template is binding per its conflict clause — this report merges both, no PR-body narrative restated",
        "os-dev.md carries the compact session-property form plus a pointer (readings live in platform-readings) rather than all three facts verbatim — zero-headroom ratchet; the adjudicated three-fact line is in platform-readings.md, which the triage ruling targeted",
        "Wording inside the two touched blocks was compressed to pay the ratchet (size-labeler sentence, objectui sentence, connective prose) — no fact dropped; four-step fallback semantics fully intact per the ruling",
        "check-governed-queue-guard reported as not measurable locally (it refuses without a CI event payload by design); its self-test is green"
      ],
      "mcp_additive_label_verb": "NO — the GitHub MCP toolset served to this session exposes no add-only label verb: issue_write labels is a full-array apply (whole set), and the only other label tool is the read-only get_label. The destructive whole-set risk for MCP-only sessions stands unconditionally, which the fact line already carries (a 403 session has no true append channel, whole-set replace only)",
      "standing_text_sweep": "os-dev.md dedup/probe text was already session-shaped (probe-first); the labels parenthetical was the only seat-type residue and is fixed. Outside the file surface: rest-channel.md's header frames its checkmarks per seat category while its own definition line already reads each checkmark as measured in a gate-open session — consistent with the session-property shape, reported rather than touched. Inside the surface but deliberately left: the platform-readings pointer phrase describing rest-channel's checkmark convention (line 104) — it describes that other file's convention, so aligning it alone would desynchronize the two files; flagged for the PM",
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  9. zhuangjianguo commented on Aug 30, 2026

    @zhuangjianguo
    Collaborator

    ACCEPT (family fold: this card + #12766) — reviewer of record: skills lane seat, session session_01EXxTW8mvPBhoHxmyPZ63de, review run at contract-review tier. PR #13367.

    Verified against GitHub — the full diff was read line-by-line by this seat:

    • os-dev.md (member [finding] the os-dev agent's standing text points devs at the ADDITIVE labels REST endpoint, which is 403 from every dev seat — every dispatch pays a wasted round-trip #12654): the seat-type parenthetical is gone; the surviving text carries the adjudicated session-property form (gate answers before the endpoint · reads and writes refused together · gate-open sessions measured 200 · probe first, readings pointered to platform-readings). The adjudicated read-back limitation is in place ("读回只检测剥除防不了,门语义标签被剥恰成绿灯"). The four-step fallback, the size-labeler counter-strip evidence, the phantom-gate sentence and the objectui carve-out all survive with semantics intact — the compression pays a zero-headroom line ratchet (466/466) and drops no fact.
    • platform-readings.md (member [finding] The additive label endpoint POST /issues/{n}/labels WORKED from a dev container (HTTP 200, read-back confirmed) — the fleet note saying it is unreachable miscites two cards that are about REST *read* channels #12766): the fact line now carries all three adjudicated facts, including "403 对具体端点零分辨力" and the four independent gate-open readings with the wider verb set (issue POST 201, comment writes); "门态逐容器变" correctly became "门态逐会话变"; the probe remains the only safe reading; the degradation ladder is untouched. 314/314, line-neutral.
    • Both cards' "no unconditional MCP guarantee" and "stop citing the two read-channel cards for writes" constraints hold on the diff. Bonus readings banked: this session's own gate-open matched pair (14-char token, GET 200 + POST 200 with read-back) — token length does not decide the channel; and the MCP toolset measured to expose NO add-only label verb, so the destructive whole-set risk stands unconditionally for MCP-only sessions, which the fact line already carries.
    • Deviations reviewed, all sound; the compact-form-plus-pointer split matches the triage ruling's own targeting (the three-fact line belongs to platform-readings). The rest-channel.md header wording flag is noted by the PM as report-only — consistent today, no card.

    Endgame (governed face, .md in diff ⇒ maintainer review path): PR stays draft; once CI on head 8d9a85f45 is fully green this seat requests review from both authorized approver accounts; listed under "awaiting a human merge" in the round report. Per the PR's own note, the base gets updated to current main with gates re-run before hand-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

domain:skillsDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions