fix(#2962): enable nullglob in for-glob shell blocks for zsh portability (#3087)

* fix(#2962): enable nullglob in for-glob shell blocks for zsh portability

Workflow shell blocks are fenced bash but execute in the user's login shell
(zsh on macOS). zsh's nomatch default aborts the WHOLE block on an unmatched
glob in a for-list (not just skipping the command), silently bypassing every
statement after it — including the verify-phase decision-coverage gate,
whose optional *-CONTEXT.md lookup used the unsafe for-list form so the
DECISION_RESULT= assignment on the next line never ran under zsh.

Fix: prepend a portable nullglob shim to every bash block containing a
for-glob loop:
  shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null
Each command no-ops (stderr suppressed) in the shell that doesn't recognize
it; the matching shell enables nullglob so an unmatched glob expands to
nothing and the loop body is skipped cleanly. Verified locally: both zsh and
bash now reach end-of-block (rc=0) on a no-match glob; bash matched-case
behavior unchanged.

14 blocks across 7 files: verify-phase.md (4, incl. the decision-coverage
gate), review.md, execute-phase.md, resume-project.md, complete-milestone.md,
audit-milestone.md, gsd-integration-checker.md, gsd-plan-checker.md (3).
Closes the zsh bypass of the #2770 fix.

* chore(#2962): add changeset fragment

* chore(#2962): backfill changeset PR number 3087

---------

Co-authored-by: sim <sim@local>
This commit is contained in:
Tom Boucher
2026-08-05 15:42:51 -04:00
committed by GitHub
parent 7203011400
commit 589a9b29b0
11 changed files with 49 additions and 10 deletions

View File

@@ -0,0 +1,10 @@
{
"version": 1,
"paths": {
"gsd-integration-checker.md": "#2962: 1 bash block (line ~98 SUMMARY iteration) gained the nullglob shim for zsh portability of the for-glob loop.",
"gsd-plan-checker.md": "#2962: 3 bash blocks (lines ~716, ~737, ~822) gained the nullglob shim for zsh portability of the for-glob loops.",
"resume-project.md": "#2962: 1 bash block (line ~66 plans-without-summaries scan) gained the nullglob shim for zsh portability of the for-glob loop.",
"complete-milestone.md": "#2962: 1 bash block (line ~221 summary one-liner extraction) gained the nullglob shim for zsh portability of the for-glob loop.",
"audit-milestone.md": "#2962: 1 bash block (line ~126 requirements_completed extraction) gained the nullglob shim for zsh portability of the for-glob loop."
}
}

View File

@@ -1,6 +1,6 @@
{
"version": 1,
"paths": {
"review.md": "#2994 (epic #1671 Phase 6.3, further amendment): fragmentizes review.md onto the marker grammar, admitting `state:reviewer-instances-configured` (shared by two peripheral notes, `reviewer-instances-note-1` in detect_clis and `reviewer-instances-note-2` in invoke_reviewers — the core reviewer-lane dispatch itself stays unmarked). Also retargets the init line from the shared `init.phase-op` to a new dedicated `init.review` entry point (cmdInitReview), which computes the reviewer-instances-configured fact via `review.reviewer_instances` config presence. Net SOURCE growth is +55 bytes (29,063 -> 29,118): two `<!-- gsd:section -->` marker-pair stubs (~331 B and ~195 B) replace their extracted prose bodies (328 B and 277 B respectively, now living in gsd-core/workflows/review/steps/*.md), and `init.phase-op` shrinks by 2 bytes to `init.review` on the gather_context init line. The EMITTED artifact composeWorkflow produces at install time still includes the extracted prose verbatim (markers strip, gap+section bodies re-join byte-for-byte) when the atom is unresolved (section_manifest null -> read-everything fallback), so installed behavior is unchanged; only the SOURCE file's on-disk byte count moves."
"review.md": "#2994 (epic #1671 Phase 6.3, further amendment): fragmentizes review.md onto the marker grammar, admitting `state:reviewer-instances-configured` (shared by two peripheral notes, `reviewer-instances-note-1` in detect_clis and `reviewer-instances-note-2` in invoke_reviewers — the core reviewer-lane dispatch itself stays unmarked). Also retargets the init line from the shared `init.phase-op` to a new dedicated `init.review` entry point (cmdInitReview), which computes the reviewer-instances-configured fact via `review.reviewer_instances` config presence. Net SOURCE growth is +55 bytes (29,063 -> 29,118): two `<!-- gsd:section -->` marker-pair stubs (~331 B and ~195 B) replace their extracted prose bodies (328 B and 277 B respectively, now living in gsd-core/workflows/review/steps/*.md), and `init.phase-op` shrinks by 2 bytes to `init.review` on the gather_context init line. The EMITTED artifact composeWorkflow produces at install time still includes the extracted prose verbatim (markers strip, gap+section bodies re-join byte-for-byte) when the atom is unresolved (section_manifest null -> read-everything fallback), so installed behavior is unchanged; only the SOURCE file's on-disk byte count moves. #2962 amendment: 2 bash blocks with for-glob loops (PLAN_FILE copy iteration ~line 254, RUN_DIR plan-file args ~line 327) gained a nullglob shim (shopt -s nullglob 2>/dev/null; setopt NULL_GLOB 2>/dev/null) so zsh no longer aborts the block on an unmatched for-list glob (+342 bytes)."
}
}