ci: honor -- end-of-options in shell-portability-lint via a word layer - #1834
Conversation
Stage 1 of the #1551 decision (recorded on the issue): build word delimitation and per-word POSIX quote removal additively on the #1544 character mask, and consume it for `--` in both trust directions. Reporting side: a word that statically unquotes to exactly `--`, sitting as a whole argv word between a hit's command word and its matched option, demotes that option to an operand; matching resumes past it. Expansions, nested frames, redirection targets, escape-spelled markers, and extents crossing a separator never suppress. Guard side: the stat fallback ladder is rejected unless its -f is proven an option BSD getopt parses (FreeBSD/macOS stat(1)): no `--` word before it — the fail-open live on main — no operand between the fallback stat and its option, no argument-taking cluster letter ahead of f, and a format argument present. Suite PASS=235 FAIL=0; shellcheck and shfmt clean; --all corpus hit set byte-identical to the origin/main scanner over the same tree. Fixes #1562. Refs #1551. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
|
Claude finished @kyle-sexton's task in 6m 56s —— View job Review: #1834 — honor
|
|
Claude finished @kyle-sexton's task in 2m 34s —— View job Security review — no security issues found
Scope reviewed: the two changed files only ( What changed: new awk functions ( Findings, by the categories in scope:
No CRITICAL, IMPORTANT, or SUGGESTION-level security findings to report for this diff. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e9b6500495
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
Two review findings against the word layer: - A demotion discarded the whole matched extent, so a real option before the `--` (`grep -P pattern -- -P`) vanished with its demoted twin and the lint passed a live GNU-only option. Only the demoted word onward is blanked now, and the resumed match re-evaluates the earlier option. - The fallback walker read a detached `-t`/`-f` argument as an operand and rejected genuine ladders like `|| stat -t "%Y" -f "%z"`. The walker now consumes the argument word, and rejects when the ladder's final `-f` itself sits in a pending argument slot. Also renames the awk-local `matchend` to `mend` (typos gate). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013dsTvnGqMJeLTZe4DpGGVQ
|
Claude finished @kyle-sexton's task in 4m 49s —— View job Review: #1834 — honor
|
|
Claude finished @kyle-sexton's task in 4m 23s —— View job Security review — 1 finding carried forward, unresolved
Scope: this repo runs a separate 🔴 Important —
|

Fixes #1562. Refs #1551 (stage 1 of the decision recorded at
#1551 (comment) —
made under owner-delegated session authority; #1551 stays open for stages 2–4).
Summary
First slice of the #1551 word layer, built additively on the #1544 character mask, consumed by
--(end-of-options) handling in both trust directions:(a quoted space never splits a word, a masked separator never splits a command) and per-word
POSIX quote removal (2.6.7; ANSI-C/locale
$-quote forms recognized as openers, content notdecoded). No re-lexing — tokens still match exactly as before.
dashdash_demoted): a matched option is discarded when a word thatstatically unquotes to exactly
--sits as a whole argv word between the hit's command wordand its matched option, inside the matched extent. Matching resumes past a discarded
occurrence, so a later real invocation on the same record is still evaluated. Everything
ambiguous keeps reporting: expansions (
$marker), nested frames on either view(
stat "$(printf '%s' x --)" -c), redirection targets (stat > -- -c), undecoded ANSI-Cescapes, and any extent that crosses a command separator.
fallback_proven): the stat fallback ladder is now rejected unless its-fis proven to be an option BSD getopt actually parses (verified against FreeBSD/macOS stat(1)):
no
--word before it — closing the fail-open that was live onmain(
stat -c '%s' "$f" || stat -- -fread as guarded) — no operand word between the fallback'sstatand its option (BSD getopt stops at the first operand), no argument-taking clusterletter ahead of
f(-tfhandsfto-tas its timefmt value), and a format argumentpresent (attached or following). Every rejection is an over-flag with the one-line
portability-ok:escape; every acceptance of those shapes was a fail-open.Both directions read a trusted input, so both are built to err toward over-flag — the posture
the token list documents.
Why now, and why this shape
#1562's five recorded failure shapes (from the three withdrawn
--attempts in #1544) all traceto three missing capabilities: per-invocation scoping, resumed matching, quoted-word
recognition. Resumed matching already landed in #1544; the other two are exactly the word layer.
The decision comment on #1551 records the full rationale, the staged plan (command-position axis
and the reporting-side short-option table deferred with triggers), and the rejected alternatives
(big-bang re-lexing rewrite; narrow guard patch; won't-fix).
An independent different-vendor review (Codex CLI, advisory) of the design contributed the
redirection-target, wrong-frame-marker, and
-tf/operand/missing-argument adversarial shapes;all are pinned as tests.
Tests
the resume and option-before-marker pins are unchanged and still pass.
substitution); 9 ambiguous shapes never suppress; regex-escape operands are never demoted;
7 unprovable fallbacks rejected on the trusted side; provable spellings (
-Lf, attached-f%z) plus every pre-existing genuine-ladder pin stay guarded.shellcheckclean on the scanner.--allover the tracked tree produces an identical hit set under theorigin/mainscanner and this branch's scanner (same tree, both scanners).
Related
decision recorded at
ci(shell-portability): the scanner cannot see command position or quoting state #1551 (comment))
Residuals (recorded in scanner comments)
A marker held in a variable (suppression side) and an ANSI-C escape-spelled marker (both sides)
are not recognized — variable indirection is out of the gate's scope throughout (#1513); both
residuals err toward over-flag.
🤖 Generated with Claude Code
https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C