Skip to content

docs(pm-skills): a dev container creates a remote branch it cannot delete — probe on the branch the dev keeps - #18808

Merged
os-justin merged 1 commit into
mainfrom
claude/issue-18774-probe-branch-undeletable
Sep 17, 2026
Merged

os-justin merged 1 commit into
mainfrom
claude/issue-18774-probe-branch-undeletable

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #18774

Clause-②: no

Two files, both fact layer, both held at their ceilings: .claude/skills/pm-dispatch/references/platform-readings.md
466 / 466 and .claude/skills/pm-dispatch/references/dispatch-runbook.md 241 / 241 — net line change 0 in
each, three rows added and three retired in the first, one added and one retired in the second, every added row ≤ 120 B.
No ceiling raised, no ruling spent. skip-changeset.claude/** is shipped by no package's files[], so nothing
published moves.

The defect

A dispatched dev's container creates a remote branch and cannot delete one on either channel, so any instruction
that says "probe before you write anything" manufactures a permanent artefact: the dev reads "before writing anything" as
"not on your working branch", pushes a throwaway, and then cannot remove it. The leftover is not inert — a
claude/issue-NNNN-* head is exactly what the AGENTS.md claim pre-check greps for, so a stray branch answers "already
claimed" for a card nobody is working. That failure direction is the silent one: a live card read as claimed, with no red
signal anywhere. This PR does not touch the capability (proxy policy is not this repo's to decide) and does not touch the
pre-check text at AGENTS.md :459–:463 (rules layer, another serial). It records the capability where branch and ref facts
live, and moves the instruction so the probe lands on the branch the dev is keeping anyway.

The capability, re-derived on this branch — ⛔ not taken from the card

Both attempts were made against this PR's own working branch, the one that stays either way. ⚠️ No branch was created
in order to fail to delete it — that is the card's whole point.

Channel ① — git, 2026-09-17T21:47Z

$ git push origin --delete claude/issue-18774-probe-branch-undeletable
✓ check:commit-card-trailers: 0 commit message(s) on this push carry no card relation and no model identifier in the trailer pair.
fatal: --negotiate-only needs one or more --negotiation-tip=*
warning: push negotiation failed; proceeding anyway with push
error: RPC failed; HTTP 403 curl 22 The requested URL returned error: 403
send-pack: unexpected disconnect while reading sideband packet
fatal: the remote end hung up unexpectedly
Everything up-to-date
$ echo $?
1

Channel ② — REST, 2026-09-17T21:47Z

$ curl -X DELETE https://api.github.com/repos/objectstack-ai/objectstack/git/refs/heads/claude/issue-18774-probe-branch-undeletable
HTTP 403
{"message":"Write access to this GitHub API path is not permitted through this proxy.","documentation_url":"https://docs.anthropic.com/en/docs/claude-code/github-actions"}

And the ref survived both, read back immediately after:

$ git ls-remote --heads origin claude/issue-18774-probe-branch-undeletable
9846f2763c2bb17cbd115643ba4f2086bf832538	refs/heads/claude/issue-18774-probe-branch-undeletable

⇒ the card's premise holds for this container, on a third branch and in a third session. One correction to the tree's
existing wording, which the rows below carry: the old row said 「容器发不出分支删除 refspec」 — the container does
send it. The RPC is made and answered 403; nothing is held back locally. Same outcome, different mechanism, and the
mechanism is what tells a reader not to go looking for a local git config to fix.

The census — the seat's, cited; and my re-take

Handed down by the domain:skills seat, 2026-09-17T21:45Z (⛔ not retyped as mine): git ls-remote --heads origin 'refs/heads/claude/issue-*' answered 291 heads; against the newest 1200 PRs (back to 2026-09-05) — 26 with an
open PR, 8 with a closed / merged PR whose branch was never deleted, 257 with no PR in that window.

My re-take, 2026-09-17T21:48Z — one ls-remote, zero writes:

$ git ls-remote --heads origin 'refs/heads/claude/issue-*' | wc -l
291

⇒ the population did not move in those three minutes. The two named strays are both present:

d5e64d8d9dae7b58bb7fa4aabccd95247b1fb9de	refs/heads/claude/issue-18545-formula-can-function
f22c63215c6935552f9761d1ec2bd59556c7b656	refs/heads/claude/issue-18734-workflow-scope-probe

f22c63215 matches the sha the card names. ⛔ I did not re-take the 26 / 8 / 257 split — that is the seat's reading,
used as an existing fact, and only the head count was re-checked for increment.

platform-readings.md — the rows

They land in the ## 读数陷阱 block at :374:379, whose subject is already refs: the row above them is the
zero-commit probe-branch reading, the row below is the two-read criterion and then the ⛔ against reading a
ls-remote | grep issue- hit as a claim. ⇒ the capability, its consequence and the claim reading now sit on the
criterion they qualify, instead of one being 57 rows away in the same section.

Added — three rows, verbatim, with byte counts

:375  114 B  - 两道皆 403:`git push --delete` 回 RPC failed,`DELETE /git/refs/heads/…` 回 not permitted,⛔ 不重试。
:376  114 B  - ⇒ 探针分支永久堆在 origin 上,只有带删权的手(维护者)清得掉,本闭环内无人有该权。
:378  117 B  - 「分支在」不是「卡已认领」:认领是 `Claim:` 评论,分支只是线索;291 个头里 257 个无 PR。

:374 is amended in place, no line bought, to carry the corrected mechanism and the replication count:

before  - 零提交的探针分支不是在飞工作:容器发不出分支删除 refspec。
after   - 零提交的探针分支不是在飞工作:容器建得出远端分支却删不掉,两会话两分支实测。   (112 B)

Paid — three rows

retired why the tree no longer needs it as a separate line what it merged into
:375 (pre-edit) — the git-channel row, spelling the failure as 「send-pack: unexpected disconnect,三次退避全败,同会话普通 push 正常」 with a placeholder branch token it is a narrative of one attempt, and its mechanism claim is the one this PR measured wrong: the disconnect is the tail of an HTTP 403, not the signal. Its 「同会话普通 push 正常」 half is the create-vs-delete asymmetry, which is what the amended :374 now states positively. Per this corpus's own 行文纪律 — 「原话仅限操作性判据;⛔ 实测叙事不进操作文本」 the added :375, which names both channels and keeps the operational ⛔
:376 (pre-edit) — 「⇒ 测量型派发留下的探针分支永久堆在 origin 上。」 it states the accumulation and stops there, leaving the reader with no disposition. The one thing a reader needs next is who can clear it, and the answer is nobody in this loop the added :376, same 「⇒」 position, same accumulation clause, plus the maintainer-only disposition
:431 (pre-edit) — 「分支删除被拒有第二形态:代理回 403,与既有 send-pack 断连同处置 ⇒ 不可删,⛔ 不重试。」 the 「第二形态」 framing exists only because the fact was discovered in two parts, 57 rows apart. Once one row names both channels, a second row whose entire content is "and there is another one of these, treat it the same" is pure discovery history. Its two operational clauses — 不可删 and ⛔ 不重试 — are both inside the added :375 the added :375

⛔ No re-wrap was used as currency: nothing here is two wrapped halves of one sentence pushed together to free a line.
Each retirement drops content — an attempt narrative, a dangling consequence, a duplicate discovered twice.

dispatch-runbook.md — the row

Added — one row, 119 B, last in ## 派发词构造细则 (:236), directly under the measurement-first row, because a
measurement-first dispatch is the shape that produced the artefact on origin today:

:236  119 B  - 探针做在 dev 要留的分支上,⛔ 不用一次性分支:建得出删不掉,遗留分支被预检读成认领。

Paid — :201 (pre-edit), 59 B

- ⛔ 不让 N 个 dev 各重跑同样贵的 GraphQL 读。

Why the tree no longer needs it as a separate line: it is the rationale half of the row above it. :200 already
carries the operational rule — 「派发那刻现取、随派发词下发的两份读数,判据同一:PM 已有的读数下发一次」 — and the
prohibition it spells is stated operationally twice more in the same block: at :201 (post-edit) 「⛔ 不让 dev 枚举
全 farm」 for the gate-list reading, and at :204 (post-edit) 「当既有事实用,只复核其后的增量,⛔ 不重跑」 for the
dedupe reading, which is also where the dev-side half of the contract is pointed at. Its cost clause is held by
:203 (post-edit) 「② 去重或看板读数取一次(查询式加时间戳)整批共用」. No rule leaves the corpus.

Reserved rows — verified by content on my head

Every change in this diff is at :374 or below, so nothing above :374 moved by a single byte or a single line.

Ratchet before / after, and the diff

node scripts/pm/check-skill-line-ratchet.mjsexit 0 both times:

file at main 9846f2763c at head de12f71b8a
platform-readings.md 466 lines (ceiling 466; headroom 0) 466 lines (ceiling 466; headroom 0)
dispatch-runbook.md 241 lines (ceiling 241; headroom 0) 241 lines (ceiling 241; headroom 0)

Widest-table-row pins are 0 for both files (neither carries a markdown table) and are unchanged.

$ git diff --stat 9846f2763c HEAD
 .claude/skills/pm-dispatch/references/dispatch-runbook.md  | 2 +-
 .claude/skills/pm-dispatch/references/platform-readings.md | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

Five for five: 4 rows rewritten or inserted against 4 retired or replaced on platform-readings.md (3 added + 1 amended
in place vs. 3 retired + 1 amended in place), 1 added against 1 retired on dispatch-runbook.md.

⏹️ For the MAINTAINER — the stray branches, and the one hand that can remove them

Both are on origin right now and no account in this loop can delete them — the dev container and the triage seat
both 403 on both channels. The two commands, to be run by a hand with delete rights:

git push origin --delete claude/issue-18734-workflow-scope-probe
git push origin --delete claude/issue-18545-formula-can-function
  • claude/issue-18734-workflow-scope-probef22c63215c6935552f9761d1ec2bd59556c7b656, one commit ahead of main, no
    PR. This is the branch the card names; it is what makes ls-remote --heads origin | grep issue-18734 answer two
    branches for one card.
  • claude/issue-18545-formula-can-functiond5e64d8d9dae7b58bb7fa4aabccd95247b1fb9de, no PR.

⛔ This PR's own branch, claude/issue-18774-probe-branch-undeletable, is not on that list — it is live work and it
carries this PR.

Gates

Derived from the worktree with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
(no hand-fed path list; change set 2 paths vs merge base 9846f2763, three-dot). All 18 run, exit code captured with
redirect-then-$?, never across a pipe:

command exit
node scripts/check-closing-keyword-parity.mjs 0
node scripts/check-closing-keyword-parity.mjs --self-test 0
node scripts/check-comment-mask-corpus.mjs 0
node scripts/pm/check-governed-queue-guard.mjs --self-test 0
node scripts/pm/check-harness-current.mjs --self-test 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions 0
pnpm check:agent-test-spelling 0
pnpm check:doc-authoring 0
pnpm check:driver-memory-census 0
pnpm check:nul-bytes 0
pnpm check:pm-governed-merges 0
pnpm check:pm-half-states 0
pnpm check:pm-skill-id-lint 0
pnpm check:pm-skill-ratchet 0
pnpm check:refd-timer-probe 0
pnpm check:required-contexts 0
pnpm check:skill-frame-sync 0
pnpm check:watch-hint-literal 0

⚠️ check:doc-formula-expressions answered exit 3 — PREREQUISITE NOT MET on its first run (「@objectstack/lint — a
workspace package that is not built」), which is not a finding and not a measurement. The gate names its own fix; it
was run: pnpm exec turbo run build --filter=@objectstack/formula --filter=@objectstack/lint --concurrency=2 through
scripts/pm/os-verify-lock.sh (VERDICT command-exit 0 · held the lock 144s · waited 0s), after which the gate answered
exit 0. The 0 above is the second run.

Reconciliation, exit codes recorded per family:

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran.txt
Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 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).

Repo-wide pnpm lint (eslint . --no-inline-config) — exit 0, run whole, not narrowed.

Control-character self-scan over both edited files, beyond check:nul-bytes:
grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' on both paths — zero matches (grep exit 1).

⛔ Not run here and not claimed: the 53 artifact-roster families, the 11 wide-population families, the 14
pending-changeset families and the CI-measured-only families the derivation prints under their own headings — CI owns
those.


Generated by Claude Code

…, so probe on the branch the dev keeps

Claude-Session: https://claude.ai/code/session_01Gqi43smmqjJ5sUrhfoPeKu
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin os-justin added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 17, 2026 — with Claude
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Sep 17, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: de12f71b8a9a852fc34e547df8eebdf6dcd52b51

Review of record on the PR thread (the queue guard's merge_group leg reads this thread and, since PR #18738, the card thread too). In-seat review of PR #18808 (#18774) by the dispatching domain:skills seat, 2026-09-17T22:32Z, read against the PR's diff, files and head on GitHub and a scratch worktree at the head — ⛔ not against the dev report (5721923148). Clause-②: no on the PR body and on the claim (5721672991); check-clause2-carriers --pair 18808 answers 0 (seat's run). Fact-layer landing: both files are under .claude/skills/pm-dispatch/references/**, so check-governed-merges reads GOVERNED and the seat lands it on this review of record — the references/** carve-out the SKILL names.

① Derived judgments

  • Scope = the claim: two files, +5 / −5 in one commit — platform-readings.md 466 / 466 and dispatch-runbook.md 241 / 241 on the head (check-skill-line-ratchet exit 0, seat's run; widest-table-row pins 0 unchanged). Every added or amended row ≤ 120 B, seat-measured without the newline: :374 112 B (amended in place), :375 114 B, :376 114 B, :378 117 B; runbook :236 119 B. Reserved rows verified by CONTENT against origin/main, seat's own md5 per band: :10–:12 SAME, PR docs(pm-skills): a repo with zero legacy statuses reads the combined-status endpoint as an empty-set default #18775's :29 SAME, PR docs(pm-dispatch): name the repository each cited instrument answers for #18666's band :209–:215 SAME (its one hunk still applies), the [Self-Approval] row :432 SAME — the insertion at :378 and the retirement at :431 cancel across it, and the dev retired :431, not :432. Overlap: docs(pm-dispatch): name the repository each cited instrument answers for #18666 remains the only open PR on this file; [finding] the reading discipline mechanically guards a ZERO but not a WRONG NON-ZERO — six instances in one shift, five of them the seat's own #18798 (in flight) edits the instrument / zero-hit rows higher in the same section, no line in common.
  • The capability, re-derived on the PR's own branch — ⛔ not taken from the card: channel ① git push origin --delete <own branch>RPC failed; HTTP 403, exit 1; channel ② DELETE /git/refs/heads/<own branch> → HTTP 403 「not permitted through this proxy」; the ref read back alive after both. Third branch, third session; no throwaway branch created to fail on. The correction to the tree is the one worth the round: the old :374 said the container 「发不出」 the delete refspec — it sends it and is answered 403, so a reader stops hunting a local git config. The seat's own census (2026-09-17T21:45Z: 291 claude/issue-* heads, 26 open PR / 8 closed PR / 257 no PR in the newest 1200) is cited as the seat's, not retyped; the dev's re-take three minutes later read 291 again and both named strays present at the shas the card gave.
  • The rows, read on the head: :374 amended to carry the mechanism and the replication count; :375 names BOTH channels with the operational ⛔ 不重试; :376 keeps the 「⇒」 accumulation clause and adds the disposition (only a hand with delete rights, i.e. the maintainer, clears it); :378 puts the claim reading on the criterion it qualifies — 「分支在」≠「卡已认领」, the Claim: comment is the claim, 257 of 291 heads carry no PR. Runbook :236, last in 派发词构造细则 under the measurement-first row: the probe is made on the branch the dev keeps, ⛔ never a throwaway — because the leftover is read as a claim by the pre-check.
  • Payment, verified as content, not re-wrap: pre-edit :375 (one attempt's narrative — 三次退避, send-pack disconnect — whose mechanism claim this PR measured wrong; its asymmetry half now in the amended :374, its ⛔ in the new :375); pre-edit :376 (the accumulation with no disposition — subsumed by the new :376); pre-edit :431 (「第二形态」 — the same fact discovered 57 rows apart, both operational clauses inside the new :375); runbook :201 「⛔ 不让 N 个 dev 各重跑同样贵的 GraphQL 读。」 (the rationale half of :200, whose rule 「PM 已有的读数下发一次」 stays, with the prohibition still stated at post-edit :201 and :204 and the cost at :203). No rule leaves the corpus; the retired :431 is not one of [finding] three 配额 rows in platform-readings.md rest on the rationale #18601 falsified (「ccr 路是 REST ⇒ 不吃 GraphQL 池」) — what they assert about a /rate_limit-red window was never measured in that direction #18744's three 配额 rows and not one of PR docs(pm-skills): the undraft ccr route eats a GraphQL pool that /rate_limit cannot see #18741's four.

② Semver level

None — no package is touched; skip-changeset on the PR is correct for .claude/**.

③ Boundary flags

Implemented-by: claude/issue-18774-probe-branch-undeletable
Reviewed-by: session_01Gqi43smmqjJ5sUrhfoPeKu

VERDICT: PASS


Generated by Claude Code

@os-justin
os-justin marked this pull request as ready for review September 17, 2026 22:32
@os-justin
os-justin added this pull request to the merge queue Sep 17, 2026
Merged via the queue into main with commit ad1f94e Sep 17, 2026
32 checks passed
@os-justin
os-justin deleted the claude/issue-18774-probe-branch-undeletable branch September 17, 2026 22:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant