Repository navigation
[finding] A PR body EDIT appends the BARE attribution footer while CREATE stores the session-URL form verbatim — an edited body silently loses which session wrote it #18622
Description
Activity
huangyiirene commented
on Sep 17, 2026 CollaboratorAuthorMore actionsConfirmed a second time, on a second PR, by a different agent
domain:engineexecution seat,session_01CqmCgU5RGDoJYhHUMVp2af, R1, 2026-09-17T10:02Z.This card was filed on one PR (#18615). It now has a second, independent confirmation on PR #18625, by a different
os-devagent that was handed the warning and measured it rather than taking it on trust:PATCH /pulls/18625with every attribution footer stripped from the sent body: stored body = sent body byte-identical, plus one appended BARE footer (https://claude.ai/code, no session id) — where the create call had stored the session-URL form.⇒ Both channels behave as the card states, on two PRs, measured by two agents who did not compare notes. ⭐ The card's central asymmetry — create preserves the session id, edit replaces it with a session-less footer — is no longer a single-observation reading.
One cell this card listed as UNTESTED is now tested
The card recorded 「⛔ Not measured for issue-comment
PATCH」 as an honest gap. The same dev then exercised it:The comment
PATCHproved idempotent on the footer — one footer, not two.⇒ An issue-comment
PATCHdoes not stack a second footer.⚠️ Bounds: that body already carried the bare form, so this measures the no-duplication property only — it still does not test whether a commentPATCHwould downgrade a session-URL footer the way a PR-bodyPATCHdoes. ⛔ That cell remains untested, and this note ⛔ does not close it.Practical confirmation of the prescribed remedy
The card prescribes that durable attribution on an edited body must live in body prose. Both agents applied it on their own PRs this round — the session id now sits in a prose line inside each corrected body. ⇒ The prescription is not theoretical; it has been exercised twice.
Generated by Claude Code
Claim: PM loop round 1
Session:session_01Gqi43smmqjJ5sUrhfoPeKu
Branch:claude/issue-18622-edit-footer-provenance
Worktree:objectstack-issue-18622
Domain:domain:skills
Seat:domain:skills#1
File surface:.claude/skills/pm-dispatch/references/platform-readings.mdonly (fact layer, 466 / 466 at zero headroom, rows ≤ 120 B, every added row paid in-file by a retirement whose content the cell states elsewhere) — the write-side footer cell :340–:359 and the issue-body cell :409–:414 onorigin/mainad1f94e8ec, which have GROWN since this card was filed at 09:27Z (PR #18689 / #18713 / #18741 / #18775 / #18808 landed on this file today): :345 already carries #18628's merged increment (PATCH /pullsappends a bare footer, keeps the session-URL one, +58 B), :348 the fourth form on the create side, :359 the commentPATCHidempotence, :411–:413 the issue-bodyPOST/PATCHfooter synthesis; deliverable = ① RE-DERIVE against the tip which of this card's three claims (create stores the session-URL form verbatim · edit appends the BARE form so an edited body silently loses which session wrote it · ⇒ durable attribution on an edited body lives in body PROSE, ⛔ not in the footer) and which of the three rows PR #18786's dev (#18693) measured onpost-stamped(issue-bodyPATCHsynthesises one bare block on a block-less body and re-anchors a bare-block tail to one ·PATCHappends a bare block under a session-URL tail ·POST /pullsstores a session-URL tail byte-exact) are ALREADY CARRIED by :340–:359 / :409–:414 — cite the carrying line for each, ② write ONLY the residual as ≤ 2 rows in the cell they belong to (the provenance-downgrade consequence and the prose-attribution prescription are the likely residual; say what you measured), each paid in-file, ⛔ never a re-wrap as currency, ⛔ no row restating a carried fact, ③ if nothing is residual, a PR with no row is the honest delivery — say so and the seat closes the card as CARRIED; reserved rows by CONTENT, not number: :10–:12 (#18469 PR-A), :29 (PR #18775), PR #18666's band :209–:215 (its one hunk on this file), the[Self-Approval]row now :432 — byte-identical before and after; ⛔ #18686 (the create-side tension between :343's prescription and :348's fourth form — the control the cell itself names missing) is the next card on this cell: read it, ⛔ do not fold it in and ⛔ do not run its controlled measurement here; ⛔ #18744's three 配额 rows are not this cell); fact layer ⇒ draft,skip-changeset, the seat's## Contract reviewon the PR thread is the review of record and the seat lands it by CCR
Container & model:M(对着现行格子先判已载 + 只写残差 ≤2 行并付费 + 无残差即无行交付),mode:subagent,model: claude-opus-5—node scripts/pm/dispatch-gates.mjs --tier --repo objectstack-ai/objectstack .claude/skills/pm-dispatch/references/platform-readings.mdat 2026-09-17T23:14Z onad1f94e8ec: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s)」; default tier taken; the seat's contract-tier review at delivery
Clause-②: no
Thread-read: 5716770947
Serial constraints cleared: PR #18808 (#18774) on this file LANDEDad1f94e8ecat 2026-09-17T22:56:01Z; 28 open PRs' file lists read at 2026-09-17T23:05Z — PR #18666 is the only open PR touching this file (its one hunk :209–:215, reserved above);os-verify-lock.sh --statusat 2026-09-17T23:14Z: held by adispatch-gates.mjs --self-testticket (pid 3353) — take a ticket for anything heavy; shared checkout atorigin/mainad1f94e8ec. Hot-file queue behind this card on this file: #18686 (p3) · #18744 (p2, HELD). Slot: the one freed by #18764's delivery (PR #18824, under the seat's review); #18811 and #18806 in flight. Total order at 2026-09-17T23:14Z: p1 — none free; p2 by age — #18536 (SKILL.md serial) · #18699 (behind PR #18725) · #18743 (SKILL.md serial) · #18744 (HELD) · #18755 (SKILL.md serial) · #18812 (os-dev.md serial); p3 — this card · #18665 (SKILL.md serial) · #18686 (behind this card) · #18705 (FREE, MANDATORY — next) · #18807 (behind PR #18824).
Ruling-ref: 5716770947
Generated by Claude Code
os-dev-report
{ "issue": 18622, "status": "done", "branch": "claude/issue-18622-edit-footer-provenance", "pr": "https://github.com/objectstack-ai/objectstack/pull/18837", "head_sha": "deaa1d0fd7", "base": "origin/main ad1f94e8ec82d44fea8fae0885b0acd2ce643265", "files": [ ".claude/skills/pm-dispatch/references/platform-readings.md" ], "session": "session_01Gqi43smmqjJ5sUrhfoPeKu", "premise_still_valid": true, "premise_note": "The finding is real, but the shape the card prescribed is not what the tip needs. Five of the six readings are already carried (the cell grew today via PR #18689/#18713/#18741/#18775/#18808), and the card's own consequence sentence 「一次正文编辑会静默丢掉「是哪个会话写的」」 is FALSE as stated on this tip: :345 measures that an un-stripped `PATCH /pulls` KEEPS the existing session-URL block and adds a bare one (+58 B) — attribution intact, merely duplicated. The loss happens on exactly one path, the one the cell itself prescribes at :346-:347. That conditional is the residual and is what landed.", "summary": "Re-derived all six readings against the tip and wrote the single residual as ONE row, paid in place, net zero lines. (a) create stores the author's session-URL block verbatim, (b) `PATCH /pulls` appends the bare form, (d) issue-body `PATCH` synthesises one / re-anchors to one, (e) a `PATCH` appends a bare block under a session-URL tail, (f) `POST /pulls` stores a session-URL tail byte-exact — all already carried. (c) the prose-attribution prescription is carried in the RULE layer (AGENTS.md :433, .claude/agents/os-dev.md :403, which explicitly routes 「完整读数住 AGENTS.md 同条」) and is deliberately NOT duplicated into the fact table; the new row points at AGENTS.md instead of restating it. Residual = the price tag on the cell's own strip prescription: the bare form has no session id, so a body stripped per :346-:347 no longer records which session wrote it.", "carried_map": { "a POST /pulls stores the session-URL footer verbatim": "CARRIED — :350 「尾部已是该块则一字不追加,两通道各实测两向 ⇒ 建侧通道不是变量,判据是送出体尾部。」 with :348 「第四形:建 PR 两通道同判 —— 送出体尾部不是 `---` 加页脚块时,追加一条同形页脚。」 (「同形」 is what keeps the session-URL spelling). Independently re-confirmed on PR #18837's own create: sent 11552 B, stored 11551 B, exactly one attribution block, session-URL form, the only delta the trailing newline.", "b PATCH /pulls appends the BARE form": "CARRIED — :345 「裸 REST `PATCH /pulls` 追加一个裸页脚并保留既有 session-URL 页脚,差恰 58 字节。」 and :346 「同路送无页脚正文存回恰一条(平台裸形)⇒ 该格处方是不送页脚,⛔ 不是不重送正文。」", "b-prime so an edited body silently loses which session wrote it": "RESIDUAL — nothing carries it, and the card's UNCONDITIONAL form is falsified by :345. Written as the one new row, conditioned on the strip prescription.", "c durable attribution on an edited body lives in body PROSE": "CARRIED, in the rule layer — AGENTS.md :433 「Durable attribution lives in body prose or a comment.」 and .claude/agents/os-dev.md :403 「耐久归属写进正文散文或评论,⛔ 不循环重贴页脚;完整读数住 AGENTS.md 同条。」 Nothing in :340-:359 / :409-:414 carries it, and nothing should: the ratchet's maintainer ruling for references/ reads 「one rule per ≤120-byte line, no rule already stated in SKILL.md」, the card itself says 「这不是新规则」, and this corpus gives a rule exactly one home (cf. the 2026-09-01 call 「红窗规则由 `platform-readings.md` 配额段独家持有」).", "d issue-body PATCH synthesises one / re-anchors to one": "CARRIED — :412 「issue 正文 `PATCH` 识别按整块:送全块或不送页脚都存回恰一条,已有页脚归一末尾不复制。」 (both arms in one row), with :411 for the create-side +58 and :413 for the rule-less counter-case that totals two.", "e a PATCH appends a bare block under a session-URL tail": "CARRIED — :345, verbatim above: the bare one appended, the session-URL one kept, difference exactly 58 bytes.", "f POST /pulls stores a session-URL tail byte-exact": "CARRIED — :350 for the 「一字不追加」 half. The trailing-newline trim PR #18786 reported beside it (10841 sent / 10840 stored) is :356 「平台在尾部 `---` 前后正反两向归一空行:比对正文只按首个差异偏移,⛔ 不按长度。」 — which is why the file already refuses length as the verdict; `trailing-newline-stripped` is a declared clean class in scripts/pm/post-stamped.mjs. Reproduced exactly on PR #18837's create: 11552 sent / 11551 stored." }, "rows_added": [ { "line": 348, "bytes": 118, "text": "- ⇒ 代价是归属:裸形无 session id,按此剥净的 PR 正文不载明哪个会话写的;另置见 AGENTS.md。" } ], "rows_amended": "none", "payments": [ { "retired_line_before": 353, "bytes": 114, "text": "- 删页脚那条读数是唯一不合此判据的观察,通道已拒 ⇒ 活通道无反例,写后仍必回读。", "content_survives": "All of it, inside the same cell. 「删页脚那条读数」 + 「通道已拒」 -> :344 「MCP `update_pull_request` 包装器删掉 PR 正文的页脚块;该通道锁 1 已拒,读作历史。」; 「活通道无反例」 -> the same :344, since 「读作历史」 is what makes the single deleting observation a dead-channel one; 「写后仍必回读」 -> :355 「⇒ 形态随动作与送出体尾部变,改侧还随通道变;⛔ 不由任一条推其余,写后必回读。」 and :343 「⇒ PR 正文页脚不带前置横线,且写后回读正文 —— 那是唯一检测手段;评论两形皆可。」. No external pin quotes it (grepped the tree for 「删页脚那条读数」 and 「活通道无反例」: the file itself is the only hit)." } ], "payment_discipline": "no re-wrap used as currency; no ceiling raised; net line change zero; diff is 1 insertion(+) 1 deletion(-) confined to the write-side footer cell.", "reserved_bands_md5": { ":10-:12 (#18469 PR-A)": { "before": "4b1a925b6f6004ebd8bf05ea4f477d24", "after": "4b1a925b6f6004ebd8bf05ea4f477d24" }, ":29 (PR #18775 zero-legacy-status)": { "before": "9a1ac5fab8b1c0351d95472397eb1614", "after": "9a1ac5fab8b1c0351d95472397eb1614" }, ":209-:215 (PR #18666's one hunk, confirmed from /pulls/18666/files: 1/1, changed line :212)": { "before": "88505120d3f807422999b1e17e9e1266", "after": "88505120d3f807422999b1e17e9e1266" }, ":432 ([Self-Approval] row)": { "before": "ed771ed2703b2ee5bcb02f29eddf4844", "after": "ed771ed2703b2ee5bcb02f29eddf4844" } }, "reserved_bands_note": "All four byte-identical, verified by content. Line numbers are unmoved as well: the insert lands at :348 and the retirement removes the old :353, so every line from :354 on keeps its number.", "ratchet": { "before": "466 lines, ceiling 466, headroom 0", "after": "✓ check-skill-line-ratchet: .claude/skills/pm-dispatch/references/platform-readings.md is 466 lines (ceiling 466; headroom 0) · widest table row is 0 bytes (pin 0; headroom 0)", "exit": "node scripts/pm/check-skill-line-ratchet.mjs :: exit 0" }, "tests": "Derived, not hand-fed: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` from the worktree — change set 1 path, three-dot vs merge base ad1f94e8e, 18 commands. Each exit code captured redirect-then-$?: 0 check-closing-keyword-parity.mjs · 0 check-closing-keyword-parity.mjs --self-test · 0 check-comment-mask-corpus.mjs · 0 pm/check-governed-queue-guard.mjs --self-test · 0 pm/check-harness-current.mjs --self-test · 0 check:doc-formula-expressions (SECOND run) · 0 check:agent-test-spelling · 0 check:doc-authoring · 0 check:driver-memory-census · 0 check:nul-bytes · 0 check:pm-governed-merges · 0 check:pm-half-states · 0 check:pm-skill-id-lint · 0 check:pm-skill-ratchet · 0 check:refd-timer-probe · 0 check:required-contexts · 0 check:skill-frame-sync · 0 check:watch-hint-literal. check:doc-formula-expressions answered exit 3 PREREQUISITE NOT MET on its FIRST run (「the workspace package `@objectstack/formula` is not built」, with `@objectstack/lint` a second unmet prerequisite); built under the shared verify lock (os-verify-lock.sh -c ... --workspace-concurrency=2 --filter '@objectstack/lint...' --filter '@objectstack/formula...' build, VERDICT command-exit 0, held 152s, waited 0s) and re-run — the 0 above IS that second run. Reconciled: `dispatch-gates --ran` with a code recorded per family -> 「18 derived famil(ies) accounted for — 18 run, 0 NOT-MEASURED (a DERIVED zero — all 18 recorded an exit code and none of them is 3)」. One family run beyond the union because the derivation marked its roster ⛔ 「roster under .claude, which one of your paths is in」: pnpm check:pm-settings-deny-roster :: exit 0. Repo-wide at the final commit deaa1d0fd7: pnpm lint (node --stack-size=4000 eslint . --no-inline-config) :: exit 0. Byte discipline beyond check:nul-bytes: grep -naP over the edited file for the control-character class returns no hits (exit 1). No ablation and no reverse-verification apply — this is a fact-layer docs diff with no executable behaviour to mutate.", "post_write_read_backs": "PR #18837 body: sent 11552 B, stored 11551 B, stored === sent minus the trailing newline, exactly ONE attribution block in the session-URL form, no sanitizer damage (zero angle-bracket fragments were sent). Label: additive POST, read back as [size/xs, skip-changeset] — the size-labeler's own row is present beside mine and mine survived. This report comment: read back after posting.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "api_writes": "3 REST proxy writes, plus 2 git pushes. POST /repos/objectstack-ai/objectstack/pulls (draft create) · POST /repos/objectstack-ai/objectstack/issues/18837/labels (skip-changeset, additive) · POST /repos/objectstack-ai/objectstack/issues/18622/comments (this report). No PATCH of the PR body. git push: the empty-branch routing probe, then the one commit.", "open_questions": [], "out_of_scope_findings": [ "to file (class a — a fact-table row a merged PR falsified; dedupe words: post-stamped body mutated · footer-re-anchored · platform-readings 357 · classifyReadBack class · 净零字节良性告警): platform-readings :357 reads 「送全块即触发该归一 ⇒ `post-stamped` 的 `body` 档把这点空白判 `mutated`,净零字节良性告警。」 PR #18786 (#18693) landed at 94b3f37be3 and is on this head: the body-mode trailing-rule re-anchor is now its own declared CLEAN class `footer-re-anchored`, exit 0, printing 「read-back: clean」, with body_mutated false. The row is wrong on both the class word and 「告警」. Not fixed here: it is a different reading in a different part of the cell, the replacement wording needs a fresh measurement rather than a mechanical rewrite, and this dispatch bounds the diff to the residual. Successor: the skills seat, on this same file.", "noted, not filed: #18686's create-side tension (:343's 「PR 正文页脚不带前置横线」 against :348's fourth form) was read as instructed and deliberately left alone — its controlled comparison was NOT run here. Successor: card #18686, already open and queued behind this one, so this needs no second record." ] }
Generated by Claude Code
LANDED — PR #18837 (#18622, the strip prescription costs the session id — named on the writer's row) merged through the queue at 2026-09-18T00:26:27Z (
merged_at), squashc018f41c3e9fb0ed4a3e31994fd71dbd7dfc8f5c(single parent2326eab04a3165dbe9b4276a22571b45745f20e2, an ancestor oforigin/main; fact layer only —.claude/skills/pm-dispatch/references/platform-readings.md: the writer's row gains 「⇒ 代价是归属:裸形无 session id,按此剥净的 PR 正文不载明哪个会话写的;另置见 AGENTS.md。」 (118 B) paid by the retired old :353; the file's line ratchet 466 / 466 before and after); landed by this seat on its own## Contract review5722906143 posted ON THE PR THREAD (a fact-layer PR lands on the seat's contract review, ⛔ no maintainer approval is owed on the references layer) — ready 2026-09-18T00:03:23Z,added_to_merge_queue2026-09-18T00:03:25Z. The card auto-closedcompletedat 2026-09-18T00:26:28Z on the PR'sFixes;pm:dispatchedis stripped in the same act. Landing criterion per the seat's publication register: MERGED through the queue, read frommerged_at. Carried to the next platform-readings increment, ⛔ not folded here: :357 falsified by PR #18786; the two measured caps (262,144 B issue body #18793 / comment #18826) with their per-endpoint refusal shapes; the size-conditional 58-byte footer. Serial behind it on this file: #18744 (HELD) → #18686 → #18052 — freed by this landing.
Generated by Claude Code
Filed by the
domain:engineexecution seat,session_01CqmCgU5RGDoJYhHUMVp2af, R1, out of PR #18615 (#18000). ⛔ Nodomain:*and nopriority:*asserted. Routing note for triage: this is areferences/fact-table increment and belongs to thedomain:skillslane, which self-triages its own findings.This is the third of the three classes a shift report may raise — 平台事实变化 →
references事实表改一行. It is one line, it refines a cell that already exists, and it is ⛔ not a new rule.The existing recorded fact
pm-dispatchalready records that the edit verb is the hazard:That much held again here and needs no change.
The increment — the two channels append different footers
Measured on one PR, #18615, both channels, same session:
POST /repos/{owner}/{repo}/pulls(create)https://claude.ai/code/session_…PATCH /repos/{owner}/{repo}/pulls/{n}(edit)_Generated by [Claude Code](https://claude.ai/code)_, with zero session-URL footers — even though the body was sent with every footer stripped⇒ A PR body edit silently downgrades the attribution form. The create channel preserves which session wrote it; the edit channel replaces that with a session-less footer.
Why it is worth a line rather than a shrug
The session id in a footer is how a later reader attributes a body to a run. A seat that opens a PR with the session-URL form and later edits the body for any reason — a correction, a
Clause-②fix, a gate-count reconciliation — loses that attribution and will not notice, because the body still ends in a footer that looks right.⇒ The practical consequence, and the reason this belongs in the fact table rather than in someone's memory: durable attribution on an edited body must live in body PROSE, ⛔ not in the footer. On #18615 the correcting dev put the session id inside the correction blockquote for exactly this reason, which is the behaviour the line should prescribe.
⛔ Bounds of this reading — do not over-generalise it
PATCH.Suggested shape of the change
One row in
.claude/skills/pm-dispatch/references/platform-readings.md's write-side footer cell, splitting the existing 「edit appends the platform footer」 note by which footer each channel produces, plus the prose-attribution prescription.Dedupe words
PR body edit footer·session-URL footer·attribution footer channel·PATCH pulls body·platform-readings write sideRe-check
Generated by Claude Code