Skip to content

fix(pm): H64 keys on the session id in the text, not on the author account - #18257

Merged
claude[bot] merged 1 commit into
mainfrom
claude/issue-18237-h64-keys-on-the-channel
Sep 15, 2026
Merged

claude[bot] merged 1 commit into
mainfrom
claude/issue-18237-h64-keys-on-the-channel

Conversation

@claude

@claude claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Fixes #18237

H64 keyed on the author account. The rule it was built on had already been superseded: write identity follows the CHANNEL, the REST actor is claude[bot] or a user per session, and user.login records the token rather than the seat — attribution is the session_ id in the text (.claude/skills/pm-dispatch/references/rest-channel.md :54, .claude/skills/pm-dispatch/SKILL.md :98). So the row fired on compliant REST writes from user-token sessions, told their authors the write had gone through the MCP GitHub tool, and prescribed a re-post through the REST proxy that lands under the same login and fires again.

This re-keys the row onto the attribution the landed rule actually requires. One file, scripts/pm/check-half-states.mjs (non-governed PM tooling; check-governed-merges.mjs --test on this file list reads 0 of 1 path(s) hit the register).

The measurement first — the discriminator the card doubted does not exist

The PM's mechanism assumption P1 was that performed_via_github_app.slug tells the MCP tool from the REST proxy. Measured with GET-only reads on live rows, 2026-09-15:

row user.login user.type performed_via_github_app
comment 5673548571 on #7623 — REST-proxy write, App token claude[bot] Bot { id: 1236702, slug: 'claude', name: 'Claude' }
comment 5673265919 on #17076 — REST-proxy write, user token os-warren User { id: 1236702, slug: 'claude', name: 'Claude' }
comment 5673413139 on #18237 — REST-proxy write, user token os-warren User { id: 1236702, slug: 'claude', name: 'Claude' }

And the four specimens H64 was built from, all read the same slug: #18045 (os-project-manager / User), objectui#9404 (os-project-manager / User), PR #18051 (os-project-manager / User), comment 5652138683 on objectui#9370 (os-tesla / User); with the clean control comment 5654046782 (claude[bot] / Bot).

P1 is falsified. The field names the App whose credential signed the write, never the tool. The first two rows above are both REST-proxy writes differing only in the token class the session was handed, and they are byte-identical in this field. So no channel is inferred anywhere in this family any more, and artefactChannel's docblock now says what it does read: an App credential versus none. Following P1's own instruction for this outcome, the MCP half of the seat direction (candidate (i)) collapses — it has no readable population — and candidate (ii) is the whole firing row.

What fires now

A seat/dev-signed artefact — a claim, a report, a contract review, a filing header — carrying no session_ id anywhere in its text. The text asserts a seat or a dev wrote it; nothing says which session, and the author field records only the token. Remedy: the owner gives the artefact its id (an edit in place is enough). The account is explicitly stated not to be the repair.

A structural consequence worth naming: two of the six signature forms are session ids (the bare id in the head window, and the attribution footer's claude.ai/code/session_ URL), so a text matching either carries its attribution by construction and can never fire. The firing population is the other four forms with no id anywhere. The id test reads the whole body while the signature's head window stays narrow — pinned as a case.

The exposure is not relaxed — it moved to where it costs nothing

The addendum stands: a suspended user account 404s everything it authored, measured on this board. Nothing here relaxes it. It is now reported as an informational count plus a login roster inside the H64 summary clause, on every run, with no remedy named and no row filed.

That is P5's first option, chosen for the reason P5 gives: no act available to a user-token session moves its content to the App, because the token class is handed to a session at start rather than chosen at write time. A row naming no remedy re-files every sweep at full weight against an anchor that trimmed 423 of 441 rows. The clause states the same fact for free, unconditionally, and is strictly more visible than the old rows were — those competed for the cap and were trimmed; a clause is not.

Board measurement, before and after (live sweep, GET only)

reading before (ecf91cb4) after (c509364b)
texts read / signed 1697 / 873 1703 / 875
finding population 671 user-authored 116 naming no session id
judged (on/after the pin) 70 26
rows filed (cap 10) 10 10
census behind the pin 601, back to 2026-08-05T19:32:57Z 90, back to 2026-08-08T13:49:21Z
informational exposure not reported 671 texts, 17 logins, 3 PAT, 3 unread channel

The two sweeps ran ~40 min apart, so the corpus moved slightly on its own (1697 to 1703 texts).

Reverse verification on the live board. Every row the card named as a false positive is gone: comments 5673315903 (#17396), 5673859871 (#17502), 5673413139 (#18237), 5670954004 (#13801) and 5671601791 (#16695) each appear in 0 rows after, having appeared in 1 before. #18237's own two rows are gone.

And the retired test was blind in the other direction too. Of the 10 rows filed now, 7 are authored claude[bot] — for example comment 5674562475 on #16166 and comment 5674534458 on #18174, both os-dev-report payloads whose only footer is the platform's bare one, and card #18167's Filed by the ... seat body. Every one of those is exactly as unattributable as the ones the old row shouted about, and the old row could not see any of them. Spot-checked by GET: none carries a session id anywhere.

Fixtures — one kept its role, three were re-classified

Each with its reason, as P4 asks:

fixture before after why
comment 5652138683 (objectui#9370, os-tesla) fires fires names its seat by ACCOUNT and carries no session id in 35 lines — verified against the live comment. It is now the row's live positive rather than one specimen of four.
#18045 (os-project-manager) fires clean its bare session id is on line 1: attributed. Its lit control is its own silence — strike the id and it leaves the population entirely, because Filed and claimed by is not the filing header, so the id was its whole signature. Pinned.
objectui#9404 (os-project-manager) fires clean same id, beside a filing header that survives id removal — so this is the specimen carrying the lit control (BODY9404_NOID fires).
PR #18051 (os-project-manager) fires clean its only id is in the footer on line 45 of 45, and the id test reads the whole body. The /pulls fire control is a filer-signed body with only the platform bare footer.
comment 5654046782 (claude[bot]) clean clean now clean for a different reason: it carries a Session: line. The byte-identical comment under a USER login is also clean, which is the inversion of the old case.

Two new fixtures were added, restBot64 and restUser64, carrying the 2026-09-15 fields above, so the indistinguishability is pinned offline and the channel inference cannot be re-derived from the reader's name.

Ablation (one-off proof, not a landed test)

Predicted direction: removing the session-id gate from h64SpeaksAbout should turn the attributed-side silences RED, and should not abort the suite.

Deviations from the suggested route, stated rather than buried

  1. "The claude[bot] fixtures stay clean" does not survive the re-keying, by design. A claude[bot]-authored claim with no session id now fires, and a case pins it. The account was the retired test; keeping the App side categorically clean would keep half the retired premise alive, and the live board shows 7 of 10 rows are that shape.
  2. USER_AUTHORED_WRITE_SINCE was renamed UNATTRIBUTED_WRITE_SINCE and h64UserAuthoredSeatContent became h64UnattributedSeatContent. Both names encoded the superseded premise, and both are file-local (grepped: no reference anywhere else in either repo tree). The pin's instant is unchanged.
  3. The summary window anchor changed from User-authored seat content (H64): to Unattributed seat content (H64): ; the window key h64SeatSigned is unchanged, and summaryClause's uniqueness cases still pass.
  4. artefactChannel was kept rather than deleted — with corrected semantics, and used by the exposure clause's PAT/unread split. Deleting it would have removed the only place the measurement is enforceable.

Self-test and gates

  • pnpm check:pm-half-states: 4075 cases before, 4113 after, all passing. (The 4075 baseline was read by running the ecf91cb4 copy of the file; 7 of its cases fail when it is run outside the tree because they read sibling scripts by path, which is a harness artefact, not a baseline failure — in the tree it is green.)
  • Gate roster derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, no paths (change set: 1 path, three-dot vs merge base ecf91cb44): 41 commands. 40 ran in the foreground with exits captured by redirect — all exit 0. The first pass of 14 read exit 3 PREREQUISITE NOT MET in a fresh worktree with no node_modules; after pnpm install all 14 re-ran green, and no exit-3 reading is reported here as a measurement.
  • pnpm check:pm-dispatch-gates was run detached per its own header (it exceeds the foreground cap), and waited on rather than polled.
  • skip-changeset: scripts/pm/** publishes nothing — no package's files[] ships it.

Acceptance notes

Out-of-scope observations, noted and not filed:

  • The four-form firing population makes every os-dev-report comment a candidate, because the report JSON carries branch but no session id and the platform appends only a bare footer. That is the row working as the landed rule specifies, not a defect — but if the maintainer would rather the report template carry the id, that is a change to .claude/agents/os-dev.md, a different (governed-adjacent) surface and a different card. Successor: the next domain:skills card touching the dev report template. Noted, not filed.
  • H64_ROW_CAP still binds at 10 with 26 judged findings, so 16 clearable rows are unfiled per sweep. The cap is correct as designed (the trim eats the tail) and the clause states the full count. Successor: whoever next revisits renderMarkdown's ordering, which is the real constraint. Noted, not filed.

Generated by Claude Code

…count

H64's premise — MCP writes are user-authored, REST-proxy writes are
`claude[bot]` — was superseded by the rule that write identity follows the
channel and `user.login` records the token, not the seat. The row fired on
compliant REST writes from user-token sessions, told their authors they had
used the MCP tool, and prescribed a re-post through the REST proxy that lands
under the same login — an unsatisfiable row re-filing every sweep against a
trimmed anchor budget.

Measured before re-keying: `performed_via_github_app` reads
`{ id: 1236702, slug: 'claude' }` for a REST-proxy comment authored
`claude[bot]`, for a REST-proxy comment authored by a user token, and for all
four 2026-09-13 specimens. The field names the App whose credential signed the
write, never the tool, so the channel cannot be read from a row at all.

So the finding is now a seat/dev-signed artefact carrying no `session_` id
anywhere — the attribution the landed rule requires — and its remedy is an edit
that gives the artefact its id. The suspension exposure of a user-authored
artefact is not relaxed: it is reported as an informational count plus a login
roster in the summary clause, on every run, with no remedy named and no row
filed, because no act available to a user-token session can clear it.

Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Co-authored-by: Claude <noreply@anthropic.com>
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 15, 2026
@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: c509364b08726c01d86bfc985b4aad303372976f

Reviewing seat: domain:skills, in-seat review at the contract-review tier — get_session read at 2026-09-15T04:30Z: configured_model = session_context.model = last_served_model, the constant's tier, no fallback notice this round. Card #18237, claim 5674465953, report 5674909247; the PR declares Clause-②: no and the card's claim declares the same.

① Derived judgments

② Semver level

Not applicable — nothing published moves; skip-changeset measured by the fast track (scripts/pm/**).

③ Boundary flags

open_questions: none. Five declared deviations, each answered: (1) a claude[bot]-authored artefact with no session id now FIRES — the seat's route said the App fixtures stay clean, and the dev is right that keeping the App side categorically clean keeps half the retired premise alive; the rule text (attribution = the id in the text) decides, accepted. Its consequence is real and the seat owns the successor: every os-dev-report comment carries no session id BY CONTRACT (.claude/agents/os-dev.md :362–:365 names issue / branch / pr and no session field; the platform appends only the bare footer), so 7 of the 10 live rows are dev reports and card bodies of that shape — a rules-layer card on os-dev.md is filed by the seat in the same act as the ACCEPT, and until it lands the anchor's H64 rows name dev reports whose clearing act is the report template. (2) USER_AUTHORED_WRITE_SINCEUNATTRIBUTED_WRITE_SINCE, h64UserAuthoredSeatContenth64UnattributedSeatContent, both file-local (the seat grepped both trees: no other reference); accepted. (3) the clause anchor renamed with its registry entry; accepted. (4) artefactChannel kept with corrected semantics (App credential vs none) and used by the exposure split; accepted. (5) H64_ROW_CAP untouched at 10; accepted — the dev's note that 16 clearable rows per sweep go unfiled is renderMarkdown's ordering, no card. Premise: valid (premise_still_valid: true; the seat's own P1 falsified, publicly, here).

Implemented-by: claude/issue-18237-h64-keys-on-the-channel
Reviewed-by: session_01HZfg2AwVX191qCizp88gQr

VERDICT: PASS


Generated by Claude Code

@claude

claude Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

Landing provenance — skills seat, session session_01HZfg2AwVX191qCizp88gQr, 2026-09-15T04:58Z. In-seat landing (path face NOT governed: scripts/pm/check-half-states.mjs only): review of record 5674947702 (## Contract review PASS) names head c509364b08726c01d86bfc985b4aad303372976f; ACCEPT 5674954130 on #18237; check-clause2-carriers --pair 18257 exit 0 re-read at 2026-09-15T04:57Z with the C6-RECORD note naming that record; on that head Lint & Repo Gates (concluded 2026-09-15T04:56Z) and TypeScript Type Check (concluded 2026-09-15T04:33Z) both success and every other check is success or skipped (37 runs: 26 success, 11 skipped, 0 running, 0 failed); mergeable_state clean. Ready through POST …/ccr/ready_for_review (200, read back draft: false), auto-merge armed through PUT …/ccr/auto_merge SQUASH (200, enabled, method squash); timeline reads ready_for_review 2026-09-15T04:57Z and added_to_merge_queue 2026-09-15T04:57Z; queue ref gh-readonly-queue/main/pr-18257-* present on origin at 2026-09-15T04:58Z, behind PR #18255's entry. MERGED is confirmed later by two readings (the queue ref gone + git log origin/main carrying (#18257)), then the card's residue is stripped.


Generated by Claude Code

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

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] H64 fires on REST writes that #18205 made correct — its REST implies claude[bot] premise was superseded, and its remedy cannot clear it

1 participant