Repository navigation
fix(scripts/pm): give os-verify-lock.sh the index execute bit its own header documents - #19050
Conversation
… header documents
The header's usage block spells five bare-path invocations
(`scripts/pm/os-verify-lock.sh --status`, `-c '<cmd>'`, `-- <argv>`,
`--show-budget`, `--self-test`), and the tracked mode 100644 refused every one
of them with exit 126 / Permission denied. Exit 126 is one digit from the 99 the
same header teaches ("this call never acquired the lock"), and it arrives from a
call that never ran the wrapped command and never printed a VERDICT line, so the
reader has a number the header does not explain.
Mode only: the blob id is unchanged (8ad1813
before and after), so `git diff --summary` is one `mode change 100644 => 100755`
line and the diff adds and removes nothing.
Measured criterion, not a blanket +x: a bare-path invocation SITE is what needs
the bit. The repo has exactly two kinds of such site — the five
`.claude/hooks/guard-*.sh` commands in `.claude/settings.json` (all already
100755) and this script's own usage block plus the PM dispatch instruction at
`.claude/skills/pm-dispatch/SKILL.md:438`. Every other execution site in the
repo (17 workflow `run:` lines, 9 `package.json` scripts) spells `bash <path>`,
so the two sibling `scripts/pm/*.sh` files stay 100644: their headers document
`bash scripts/pm/…` and that spelling was never refused.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF
Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
|
Landing record — landed on the seat's record through the CCR route (skills seat, Channel: the allow rules of PR #19047 on disk under the maintainer's manual-confirmation mode (open since 2026-09-18T14:25Z); the act typed in the rules' literal-prefix shape. Pre-checks: NOT governed ( Acts: Generated by Claude Code |
Fixes #19044
Clause-②: no
scripts/pm/os-verify-lock.shgoes100644→100755in the index. Content isbyte-identical: the blob id is
8ad1813feff415727c4bd16f834707d20c6fc34cbefore andafter, so the diff adds and removes nothing.
The dispatch named three modes to flip. The criterion the card asks for first was
measured before any edit, and it places one of them. The two siblings are left at
100644on purpose.Their own headers do not document a bare-path invocation. Verbatim, from the usage
blocks:
100644?scripts/pm/os-verify-lock.shscripts/pm/os-verify-lock.sh --status(and 4 more bare-path lines)scripts/pm/ensure-pm-labels.shbash scripts/pm/ensure-pm-labels.sh·bash … --reconcilescripts/pm/os-regen-merge.shbash scripts/pm/os-regen-merge.sh·bash … --self-testSo the card's two "controls" widened the mode reading (all three are
100644— true,re-measured here) but they do not widen the class-(b) contract violation: there is
exactly one script in this repo whose own documented spelling the tracked mode refuses.
The card itself set the boundary this follows — 「A blanket
+xover everything would bethe same unexamined move in the other direction」 — and flipping two files whose documented
spelling was never refused is that move, scoped to one lane.
The criterion, measured
A bare-path invocation SITE is what needs the bit — not a header's prose, and not a
lane's habit. Two kinds of such site exist in this repo, and nothing else executes a
tracked
.shby bare path:.claude/settings.jsonhook commands — 5 of them, each spelled as the quoted path withno interpreter. All five files are already
100755.scripts/pm/os-verify-lock.sh's own usage block (5 lines), plus the PM dispatchinstruction at
.claude/skills/pm-dispatch/SKILL.md:438, which spells the fullbare path
scripts/pm/os-verify-lock.sh --status.Every other execution site spells
bash PATH: 17 workflowrun:lines (includinglint.yml:1698andlint.yml:1779, the self-test gates for two of these three scripts)and 9
package.jsonscript entries. Both.githooks/references are prose, not calls.⭐ That second site is the one the triage comment flagged as its source-side upgrade
condition — 「若测到另有文档/模板/派发令在教人用直调拼法」. It exists:
SKILL.md:438teaches the refused spelling to the PM seat. This PR resolves thatcondition rather than escalating it (the spelling now runs), and the reading is handed
to the seat rather than acted on: priority is not this PR's to move.
Every tracked
.sh— mode · shebang · does its header document a bare pathAll 29 tracked
.shfiles carry the shebang#!/usr/bin/env bash. Header = first 40 lines,self-path in command-word position.
.claude/hooks/guard-main-checkout.shsettings.json).claude/hooks/guard-main-checkout-bash.shsettings.json).claude/hooks/guard-process-kill.shsettings.json).claude/hooks/guard-shared-stash.shsettings.json).claude/hooks/guard-tree-enum.shsettings.json).claude/hooks/guard-main-checkout.selftest.sh.claude/hooks/guard-main-checkout-bash.selftest.sh.claude/hooks/guard-process-kill.selftest.sh.claude/hooks/guard-shared-stash.selftest.sh.claude/hooks/guard-tree-enum.selftest.shscripts/build-console.shscripts/bump-objectui.shscripts/collect-release-notes.shscripts/bump-objectui.selftest.shbash×1)scripts/ci/select-shard-packages.shbash×2)scripts/create-scaffold-smoke.shscripts/downstream-smoke.shscripts/gen-sdui-manifest.shscripts/release-publish.shscripts/release-spec-changes.shbash×3)scripts/vercel-ignore-docs.shscripts/vercel-ignore-docs.selftest.shbash×1)scripts/pm/os-verify-lock.shscripts/pm/ensure-pm-labels.shbash×2)scripts/pm/os-regen-merge.shbash×2)scripts/ci/select-gate-families.shbash×3)scripts/ci/select-gate-families.selftest.shbash×1)scripts/ci/select-shard-packages.selftest.shbash×1)scripts/publish-smoke.shCounts on
origin/mainatabb01f105: 29 tracked.sh— 22 at100755, 7 at100644. (The triage comment's contrast figure of 24 is off by two; the card's and thedispatch's three
scripts/pm/rows re-measure exactly.)⇒ After this PR the criterion holds with zero violations across all 29 files, which is
the state shape C would assert if a gate were ever taken. Shape C is not taken here.
Exit codes — before / after,
bash PATHas the control both timesMeasured in this worktree, branched from
origin/mainatabb01f105.100644)100755)scripts/pm/os-verify-lock.sh --statusPermission denied)bash scripts/pm/os-verify-lock.sh --status— controlscripts/pm/os-verify-lock.sh --show-budgetbash scripts/pm/os-verify-lock.sh --show-budget— controlscripts/pm/os-verify-lock.sh --self-testall cases pass)bash scripts/pm/os-verify-lock.sh --self-test— controlall cases pass)And the two files left at
100644, showing the absence of a violation in both directions:bash scripts/pm/os-regen-merge.sh --help— the documented spellingbash scripts/pm/ensure-pm-labels.sh --help— the documented spellingscripts/pm/os-regen-merge.sh --help— bare path, documented nowherescripts/pm/ensure-pm-labels.sh --help— bare path, documented nowhereReader test (one line): a dev copies
scripts/pm/os-verify-lock.sh --statusout of theheader, pastes it at a worktree root, and it prints the lock state and exits 0 — on this
branch it does; on
origin/mainit printsPermission deniedand exits 126.The diff
git diff --staton a mode-only change is notempty — it prints the file with a zero-width graph and
0 insertions(+), 0 deletions(-).--summaryis the line that carries the change.Mechanism this is built on, measured not assumed
scripts/pm/**file mode. Grepping100755/100644/fileMode/update-index --chmod/X_OKoverscripts/**,.github/workflows/**,packages/lint/**,.githooks/**and.claude/hooks/**returns mode assertions forexactly two paths, both under
.githooks/:check-regen-pending.mjs:1615asserts.githooks/pre-pushis100755, andgit-merge-regen.mjs:1212asserts the same for.githooks/pre-commit. Both prescribe the fix asgit update-index --chmod=+x— thisrepo already treats an index execute bit as an assertable contract, with this exact
remedy, so shape A's mechanism is the sanctioned one and not a new idea.
scripts/pm/check-widening-tells.mjs:4263pins 「a MODE-ONLY change carries a real 0」 and:4280pins that a mode-only change to a tell surface isclean.scripts/pm/*.share#!/usr/bin/env bash, so the newlyexecutable file runs under
bash, notsh. All 29 tracked.shcarry the same shebang.core.fileModeistruein this worktree, sochmod +xplusgit addwas thespelling used;
git ls-files -sconfirms the index carries100755, and the worktreeand index agree (no phantom "mode change back" in
git status).(
lint.yml:1698,lint.yml:1779) both spellbash PATH, so neither is affected ineither direction by a mode flip.
Acceptance notes
Observations from the criterion sweep, not filed and not changed here:
100644/100755split among the files with no bare-path site is arbitrarydrift, not a rule:
scripts/ci/select-shard-packages.shis100755while its siblingscripts/ci/select-gate-families.shis100644, and both are invoked only asbash PATHfromlint.yml/ci.yml. Two of the threescripts/ci/*.selftest.share100644, thescripts/selftests elsewhere are100755. Nothing is broken by it and nodeclared contract is crossed — the criterion is one-directional, so a file carrying
+xit does not need is not a violation.
scripts/pm/os-regen-merge.sh:393spells its--helpusage with a bare basename(
os-regen-merge.sh --self-test), which would not run even with the bit set, since thebasename is not on
PATH. It is a usage label, not a path invocation, so it is outsidethe criterion — noting it as the boundary, not widening the class.
scripts/pm/is27×100644and1×100755(release-rehearsal-clone.mjs), so「
+xis the repo's convention」 is true repo-wide for.sh(22 of 29) and false for thislane. The criterion, not the lane habit, is what places the bit.
Changeset
skip-changeset:scripts/pm/**publishes nothing — no package'sfiles[]ships it, andthe diff is a file mode, so no released byte moves. Fast-track path, no measurement needed.
Generated by Claude Code