Skip to content

fix(pm): drop the phantom # pathspec from os-regen-merge.sh's .gitattributes reader - #15700

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15497-os-regen-list-phantom-pathspec
Sep 5, 2026
Merged

fix(pm): drop the phantom # pathspec from os-regen-merge.sh's .gitattributes reader#15700
baozhoutao merged 1 commit into
mainfrom
claude/issue-15497-os-regen-list-phantom-pathspec

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15497

scripts/pm/os-regen-merge.sh built its authoritative run-time path list with
grep 'merge=os-regen' .gitattributes | awk '{print $1}'. That also matched the header
comment which quotes the literal in prose, took ITS first field, and put a pathspec that was
literally # into the list — carried on into the two git diff --name-only calls that decide,
per file, which side step 2 takes, and into the pattern COUNT the script prints to the operator.

Counts, re-derived at pickup (⛔ neither 18 nor 19 from the thread is current)

Measured on origin/main at 95d5cbb31, running the script's own pipeline verbatim:

reading
total entries produced (old reader) 19
entries literally # 1
real merge=os-regen rows in the file 18
entries produced (repaired reader) 18

diff of the two lists is exactly one line: the leading #. Two independent corroborations of
18 — the 18 rows at .gitattributes:140-157, and node scripts/git-merge-regen.mjs --self-test,
whose own reader is separate from this one: ✓ .gitattributes ↔ regen-artifacts.mjs agree on 18 path(s).

The grammar, MEASURED not assumed (git 2.43.0, scratch repos)

Triage asked for the repair to be decided against the file's real grammar rather than against the
two lines that happen to exist today. Three readings:

  1. A leading-# line is a comment, and indenting does not undo that. # foo bar assigns
    nothing to a file named #; the escaped-pattern control \# foo bar on that same file assigns
    foo and bar. So comment lines must be dropped before the field split, and the strip has
    to tolerate leading whitespace.
  2. There is NO inline trailing comment. x merge=os-regen # trailing note does not route x
    with a note on the end — git rejects the whole row: # is not a valid attribute name: .gitattributes:1 on stderr, and git check-attr merge -- x then answers unspecified. So
    there is nothing to strip: such a row can never be live. This is the reading the dispatch left
    conditional ("and inline trailing comments if admitted"); measured, they are not admitted.
  3. Attributes are whitespace-delimited tokens, so merge=os-regen-v2 is a different driver
    (git confirms: x: merge: os-regen-v2), not this one.

The repair chosen, and why

One awk in place of grep | awk, doing both jobs in the tool that already does the field split:

    /^[ \t]*#/ { next }
    /(^|[ \t])merge=os-regen([ \t]|$)/ { print $1 }
  • Strip comment lines rather than anchor with grep '^[^#]': reading 1 says an indented #
    is still a comment, and ^[^#] admits it.
  • Token-anchor the attribute rather than substring-match it: reading 3 says a bare substring
    admits a different driver's rows.
  • No inline-comment handling: reading 2 says no such row is ever live. The reading is pinned
    in the self-test against real git, so the day git grows one, this decision reddens instead of
    rotting.
  • [ \t] rather than [[:space:]]: the bash 3.2 floor documented a few lines above is a macOS
    floor, and that host's awk is not gawk.
  • It matches the rule scripts/git-merge-regen.mjs's own reader (reconcileAttributes) already
    applies — that reader was already comment-safe, so this is the shell side catching up, not a
    new convention.

The printed count at what is now :265 therefore equals the number of real patterns: 18.

Self-test growth

bash scripts/pm/os-regen-merge.sh --self-test, before and after, both verdicts quoted:

cases ok FAIL verdict line
before (the parent commit's copy of the script) 39 0 ✓ os-regen-merge self-test: all cases pass.
after 51 0 ✓ os-regen-merge self-test: all cases pass.

Twelve new cases, in three groups:

  • 8 — the list itself. A bait .gitattributes carrying prose that quotes the literal (two
    shapes: backticked, as the real file's line 36 has it, and whitespace-delimited, the near
    neighbour a future edit adds for free) plus exactly three real rows. The assertion is the
    exact printed COUNT (3), never "no entry equals #" — as triage required, a count also
    reds the day the read silently matches ZERO lines, which an absence assertion passes happily.
    A companion case shows the fixture really is bait: the pre-repair spelling over-counts that same
    file at 5.
  • 8a — the grammar. The inline-trailing-comment measurement above, pinned against real git,
    with a firing control (the same row without the note DOES route) so a git that stopped reading
    .gitattributes at all could not pass the two absence assertions.
  • 8b — the discriminating mutation. Same perl/\Q..\E literal-replacement shape case 6b
    already uses: disable the reader's comment-line skip in a copy of the script, run it against the
    bait fixture, and assert the phantom is back and the count is one too high.

Ablation — the pin can fail

Trap-guarded (trap ... EXIT INT TERM), absolute paths resolved from git rev-parse --show-toplevel, mutation proven on disk in both directions:

PRE  head-blob=562c20f19ea10a21ac9e9770e57a36492eed027f worktree=562c20f19ea10a21ac9e9770e57a36492eed027f
ANCHOR occurrences of the repaired reader: 1
MUTATION written: repaired awk reader -> pre-repair grep|awk one-liner
POST worktree=90384a92b50256ae9877180070a8450a6b3db22c
on-disk: old one-liner present = 1
ablated-selftest-exit=1

Verdict under ablation — ✗ os-regen-merge self-test: 4 case(s) failed.:

FAIL  the pattern count counts ROWS, not prose that quotes the literal
      got:  5      want: 3
FAIL  and no phantom # pathspec is listed
      got:  2      want: 0
FAIL  mutated: the phantom # is back in the list (proves case 8 bites)
      got:  2      want: 1
FAIL  mutated: and the count is one too high
      got:  5      want: 4

Restore leg, verified rather than trusted: worktree blob back to
562c20f19ea10a21ac9e9770e57a36492eed027f, equal to the HEAD blob, and git diff HEAD empty.

Gates

Derived on the final head with node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack (which re-derives its own change set from the merge base — no
hand-built path list): 23 commands, all exit 0. Plus pnpm check:pm-dispatch-gates
✓ dispatch-gates self-test: 1445 cases pass. (exit 0), pnpm check:nul-bytes (exit 0, and a
separate grep -naP control-byte scan of the edited file returned exit 1 = none), and
node scripts/git-merge-regen.mjs --self-test (exit 0) as the sibling-reader corroboration.
Every exit code was captured before any pipe.

CI's own wiring for this script — .github/workflows/lint.yml:1364, bash scripts/pm/os-regen-merge.sh --self-test — is exactly the command run above.

NOT MEASURED, by name:

  • The first pass of the 23-command family, before pnpm install had ever run in this worktree:
    six of them answered PREREQUISITE NOT MET / ERR_MODULE_NOT_FOUND (exit 3 and 1). Those are
    reported here as NOT MEASURED, never as reds; all 23 were re-run green after the install.
  • Two earlier verify-lock acquisitions returned VERDICT queue-timeout (exit 99) · never acquired
    — nothing was built, tested or decided by them.
  • The three dispatch-gates families whose argv takes a value from the workflow
    (check-cross-package-test-inputs --union-into "$RUNNER_TEMP/...",
    check-shard-attestation, check-test-completeness) — the tool prints them as ⊘ NOT MEASURED
    and they belong to CI.
  • The always-runs tail with no path filter, and the 37 artifact-roster families whose silent
    verdict is a fact about a list rather than about these paths.

Out of fence

No changeset: this changes no published package, only PM merge tooling. skip-changeset applied.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

… reader

`grep 'merge=os-regen' .gitattributes | awk '{print $1}'` also matched the
header comment that quotes the literal in prose and took ITS first field, so
the run-time path list carried a pathspec that was literally `#`. Harmless by
luck — `#` matches no tracked path, so both `git diff --name-only … --
"${regen_paths[@]}"` calls ignored it — but the pattern COUNT printed to the
operator is the only check that the script is reading the right surface, and it
was off by one in the one place the design deliberately keeps no second copy.

Measured on origin/main 95d5cbb: 19 entries produced, 1 literally `#`, 18
real patterns. The repaired reader answers 18, matching both the 18 rows in the
file and `scripts/git-merge-regen.mjs`'s independent reader.

The grammar was measured, not assumed (git 2.43.0, scratch repos): a leading-`#`
line is a comment even when indented, and there is NO inline trailing comment —
git rejects such a row whole and routes nothing. Hence comment lines are dropped
before the field split, and `merge=os-regen` is anchored as a whitespace-
delimited token.

Pinned in the script's own `--self-test`: a bait `.gitattributes` with prose
quoting the literal plus three real rows, asserting the exact printed COUNT (so
an empty population reds too), the git-grammar measurement with a firing
control, and a discriminating mutation that disables the comment skip and
watches the phantom come back.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk.

Reviewed against the three-dot diff at 44a0a4fb5 (1 file, +132/−1), not the report. Closing keyword: Fixes #15497 only.

What the diff does: scripts/pm/os-regen-merge.sh's reader replaces grep 'merge=os-regen' .gitattributes | awk '{print $1}' with one awk that drops comment lines before the field split and matches merge=os-regen only as a whitespace-delimited token (/(^|[ \t])merge=os-regen([ \t]|$)/). The repair is argued against a MEASURED grammar (git 2.43.0 in scratch repos): an indented # line is still a comment, an escaped \# pattern still assigns, and there is NO inline trailing comment — git rejects such a row whole (is not a valid attribute name) — so the dispatch's conditional clause resolves to "not admitted", stated with the control. The --self-test gains three legs: the exact printed COUNT over a bait .gitattributes whose prose quotes the literal (8), the grammar measurement with its firing control (8a), and a discriminating mutation that disables the comment skip (8b). scripts/git-merge-regen.mjs untouched (its reader is not a shared copy and already skips comments; its separate word-boundary over-admission is filed as #15701). #14671 is CLOSED — no rider.

Measured by this seat on the PR head (plain bash, detached worktree; readings copied from the run output):

bash scripts/pm/os-regen-merge.sh --self-test   EXIT=0   ✓ os-regen-merge self-test: all cases pass.  (last case line: "ok    mutated: and the count is one too high")
old construction on the real .gitattributes at the PR head:  19 entries, 1 literally '#'   (premise reproduced at pickup)

Not measured here: the derived family (dev: 23 commands, all exit 0 after install; dispatch-gates self-test: 1445 cases pass) — CI's Lint & Repo Gates (which runs bash scripts/pm/os-regen-merge.sh --self-test at lint.yml:1364) is the arbiter; TypeScript Type Check already success at review time.

Fences held: one file; check-governed-merges.mjs --test0 of 1 path(s) hit the register; no other open PR holds it; git merge-tree origin/main clean; skip-changeset (nothing published). Flip + arm once both required jobs read success.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 5, 2026 03:47
@baozhoutao
baozhoutao enabled auto-merge September 5, 2026 03:47
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit da28b62 Sep 5, 2026
36 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-15497-os-regen-list-phantom-pathspec branch September 5, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m 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] scripts/pm/os-regen-merge.sh admits a phantom pathspec — its os-regen list is 18 entries where .gitattributes has 17 real patterns

2 participants