Skip to content

Commit 0ec8185

Browse files
os-elon-muskclaude
andauthored
fix(scripts/pm): give os-verify-lock.sh the index execute bit its own header documents (#19050)
Fixes #19044 Clause-②: no `scripts/pm/os-verify-lock.sh` goes `100644` → `100755` in the index. Content is byte-identical: the blob id is `8ad1813feff415727c4bd16f834707d20c6fc34c` before and after, so the diff adds and removes nothing. ## ⚠️ Deviation from the dispatched file surface — one file, not three 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 `100644` on purpose. **Their own headers do not document a bare-path invocation.** Verbatim, from the usage blocks: | file | its header's usage spelling | refused at `100644`? | |:--|:--|:--| | `scripts/pm/os-verify-lock.sh` | `scripts/pm/os-verify-lock.sh --status` (and 4 more bare-path lines) | **yes — exit 126** | | `scripts/pm/ensure-pm-labels.sh` | `bash scripts/pm/ensure-pm-labels.sh` · `bash … --reconcile` | no — exit 0 | | `scripts/pm/os-regen-merge.sh` | `bash scripts/pm/os-regen-merge.sh` · `bash … --self-test` | no — exit 0 | So 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 `+x` over everything would be the 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 `.sh` by bare path: 1. `.claude/settings.json` hook commands — 5 of them, each spelled as the quoted path with no interpreter. All five files are already `100755`. 2. `scripts/pm/os-verify-lock.sh`'s own usage block (5 lines), plus the PM dispatch instruction at `.claude/skills/pm-dispatch/SKILL.md:438`, which spells the full bare path `scripts/pm/os-verify-lock.sh --status`. Every other execution site spells `bash PATH`: **17** workflow `run:` lines (including `lint.yml:1698` and `lint.yml:1779`, the self-test gates for two of these three scripts) and **9** `package.json` script 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:438` teaches the refused spelling to the PM seat. **This PR resolves that condition 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 path All 29 tracked `.sh` files carry the shebang `#!/usr/bin/env bash`. Header = first 40 lines, self-path in command-word position. | file | mode | header documents bare path | verdict | |:--|:--|:--|:--| | `.claude/hooks/guard-main-checkout.sh` | 100755 | no (invoked bare from `settings.json`) | ok | | `.claude/hooks/guard-main-checkout-bash.sh` | 100755 | no (invoked bare from `settings.json`) | ok | | `.claude/hooks/guard-process-kill.sh` | 100755 | no (invoked bare from `settings.json`) | ok | | `.claude/hooks/guard-shared-stash.sh` | 100755 | no (invoked bare from `settings.json`) | ok | | `.claude/hooks/guard-tree-enum.sh` | 100755 | no (invoked bare from `settings.json`) | ok | | `.claude/hooks/guard-main-checkout.selftest.sh` | 100755 | yes (1) | ok | | `.claude/hooks/guard-main-checkout-bash.selftest.sh` | 100755 | yes (1) | ok | | `.claude/hooks/guard-process-kill.selftest.sh` | 100755 | yes (1) | ok | | `.claude/hooks/guard-shared-stash.selftest.sh` | 100755 | yes (1) | ok | | `.claude/hooks/guard-tree-enum.selftest.sh` | 100755 | yes (1) | ok | | `scripts/build-console.sh` | 100755 | yes (1) | ok | | `scripts/bump-objectui.sh` | 100755 | yes (4) | ok | | `scripts/collect-release-notes.sh` | 100755 | yes (4) | ok | | `scripts/bump-objectui.selftest.sh` | 100755 | no (`bash` ×1) | ok | | `scripts/ci/select-shard-packages.sh` | 100755 | no (`bash` ×2) | ok | | `scripts/create-scaffold-smoke.sh` | 100755 | no | ok | | `scripts/downstream-smoke.sh` | 100755 | no | ok | | `scripts/gen-sdui-manifest.sh` | 100755 | no | ok | | `scripts/release-publish.sh` | 100755 | no | ok | | `scripts/release-spec-changes.sh` | 100755 | no (`bash` ×3) | ok | | `scripts/vercel-ignore-docs.sh` | 100755 | no | ok | | `scripts/vercel-ignore-docs.selftest.sh` | 100755 | no (`bash` ×1) | ok | | **`scripts/pm/os-verify-lock.sh`** | **100644 → 100755** | **yes (5)** | **the one violation — fixed here** | | `scripts/pm/ensure-pm-labels.sh` | 100644 | no (`bash` ×2) | ok — left alone | | `scripts/pm/os-regen-merge.sh` | 100644 | no (`bash` ×2) | ok — left alone | | `scripts/ci/select-gate-families.sh` | 100644 | no (`bash` ×3) | ok | | `scripts/ci/select-gate-families.selftest.sh` | 100644 | no (`bash` ×1) | ok | | `scripts/ci/select-shard-packages.selftest.sh` | 100644 | no (`bash` ×1) | ok | | `scripts/publish-smoke.sh` | 100644 | no usage line at all | ok | Counts on `origin/main` at `abb01f105`: 29 tracked `.sh` — **22** at `100755`, **7** at `100644`. (The triage comment's contrast figure of 24 is off by two; the card's and the dispatch'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 PATH` as the control both times Measured in this worktree, branched from `origin/main` at `abb01f105`. | invocation | before (`100644`) | after (`100755`) | |:--|:--|:--| | `scripts/pm/os-verify-lock.sh --status` | **126** (`Permission denied`) | **0** | | `bash scripts/pm/os-verify-lock.sh --status` — control | 0 | 0 | | `scripts/pm/os-verify-lock.sh --show-budget` | 126 | **0** | | `bash scripts/pm/os-verify-lock.sh --show-budget` — control | 0 | 0 | | `scripts/pm/os-verify-lock.sh --self-test` | 126 | **0** (`all cases pass`) | | `bash scripts/pm/os-verify-lock.sh --self-test` — control | 0 | 0 (`all cases pass`) | And the two files left at `100644`, showing the absence of a violation in both directions: | invocation | exit | |:--|:--| | `bash scripts/pm/os-regen-merge.sh --help` — the documented spelling | 0 | | `bash scripts/pm/ensure-pm-labels.sh --help` — the documented spelling | 0 | | `scripts/pm/os-regen-merge.sh --help` — bare path, documented nowhere | 126 | | `scripts/pm/ensure-pm-labels.sh --help` — bare path, documented nowhere | 126 | **Reader test (one line):** a dev copies `scripts/pm/os-verify-lock.sh --status` out of the header, pastes it at a worktree root, and it prints the lock state and exits 0 — on this branch it does; on `origin/main` it prints `Permission denied` and exits 126. ## The diff ``` $ git diff --summary abb01f1 HEAD mode change 100644 => 100755 scripts/pm/os-verify-lock.sh $ git diff --stat abb01f1 HEAD scripts/pm/os-verify-lock.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) ``` ⚠️ One correction to a stated expectation: `git diff --stat` on a mode-only change is **not empty** — it prints the file with a zero-width graph and `0 insertions(+), 0 deletions(-)`. `--summary` is the line that carries the change. ## Mechanism this is built on, measured not assumed - **No gate or CI job reads a `scripts/pm/**` file mode.** Grepping `100755` / `100644` / `fileMode` / `update-index --chmod` / `X_OK` over `scripts/**`, `.github/workflows/**`, `packages/lint/**`, `.githooks/**` and `.claude/hooks/**` returns mode assertions for exactly two paths, both under `.githooks/`: `check-regen-pending.mjs:1615` asserts `.githooks/pre-push` is `100755`, and `git-merge-regen.mjs:1212` asserts the same for `.githooks/pre-commit`. Both prescribe the fix as `git update-index --chmod=+x` — this repo 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. - **A mode-only diff is already pinned as clean** by the widening-tells gate: `scripts/pm/check-widening-tells.mjs:4263` pins 「a MODE-ONLY change carries a real 0」 and `:4280` pins that a mode-only change to a tell surface is `clean`. - **Shebangs:** all three `scripts/pm/*.sh` are `#!/usr/bin/env bash`, so the newly executable file runs under `bash`, not `sh`. All 29 tracked `.sh` carry the same shebang. - **`core.fileMode` is `true`** in this worktree, so `chmod +x` plus `git add` was the spelling used; `git ls-files -s` confirms the **index** carries `100755`, and the worktree and index agree (no phantom "mode change back" in `git status`). - **Serial safety:** the two self-test gates that run these scripts in CI (`lint.yml:1698`, `lint.yml:1779`) both spell `bash PATH`, so neither is affected in either direction by a mode flip. ## Acceptance notes Observations from the criterion sweep, not filed and not changed here: - The `100644` / `100755` split among the files with **no** bare-path site is arbitrary drift, not a rule: `scripts/ci/select-shard-packages.sh` is `100755` while its sibling `scripts/ci/select-gate-families.sh` is `100644`, and both are invoked only as `bash PATH` from `lint.yml` / `ci.yml`. Two of the three `scripts/ci/*.selftest.sh` are `100644`, the `scripts/` selftests elsewhere are `100755`. Nothing is broken by it and no declared contract is crossed — the criterion is one-directional, so a file carrying `+x` it does not need is not a violation. - `scripts/pm/os-regen-merge.sh:393` spells its `--help` usage with a **bare basename** (`os-regen-merge.sh --self-test`), which would not run even with the bit set, since the basename is not on `PATH`. It is a usage label, not a path invocation, so it is outside the criterion — noting it as the boundary, not widening the class. - `scripts/pm/` is `27` × `100644` and `1` × `100755` (`release-rehearsal-clone.mjs`), so 「`+x` is the repo's convention」 is true repo-wide for `.sh` (22 of 29) and false for this lane. The criterion, not the lane habit, is what places the bit. ## Changeset `skip-changeset`: `scripts/pm/**` publishes nothing — no package's `files[]` ships it, and the diff is a file mode, so no released byte moves. Fast-track path, no measurement needed. --- _Generated by [Claude Code](https://claude.ai/code/session_01BTeBejoPUvRHN8WdAJC6oF)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4073030 commit 0ec8185

1 file changed

File tree

‎scripts/pm/os-verify-lock.sh‎

100644100755
File mode changed.

0 commit comments

Comments
 (0)