Skip to content

fix(bin): parse fm-brief.sh and friends under stock macOS Bash 3.2 - #1196

Open
theaussie86 wants to merge 1 commit into
kunchenguid:mainfrom
theaussie86:fm/fm-brief-bash32-parse
Open

fix(bin): parse fm-brief.sh and friends under stock macOS Bash 3.2#1196
theaussie86 wants to merge 1 commit into
kunchenguid:mainfrom
theaussie86:fm/fm-brief-bash32-parse

Conversation

@theaussie86

Copy link
Copy Markdown

What Changed

  • Converted $(cat <<'EOF' ...) heredoc captures in bin/fm-brief.sh and bin/fm-bootstrap.sh to IFS= read -r -d '' VAR <<'EOF' plus a trailing-newline strip, so prose containing apostrophes no longer breaks the parse under stock macOS Bash 3.2 (bin/fm-brief.sh previously failed with "unexpected EOF" and exited 2 without scaffolding a brief).
  • Added a Bash 3.2 parse sweep to bin/fm-lint.sh: when /bin/bash exists it runs bash -n over bin/*.sh and bin/backends/*.sh, normalizing $ROOT/ and ./ path prefixes first, so the pre-push lint gate owns the invariant instead of only the macOS CI lane.
  • Widened the macOS CI job to parse-check every bin script, and documented the rule in .agents/skills/firstmate-coding-guidelines/SKILL.md and CONTRIBUTING.md.
  • Added tests/fm-lint.test.sh coverage for the sweep (guard presence, path-spelling normalization, and a 3.2-only defect probe gated on /bin/bash actually being Bash 3.x, with cleanup via the repo's FM_TEST_CLEANUP_DIRS trap convention); updated tests/fm-brief.test.sh for the converted blocks. The 3.2-only probe and the pinned-ShellCheck tests skip on machines without those toolchains, so their runtime behavior is proven by CI's macOS lane.

Risk Assessment

✅ Low: The round-3 blocker is correctly resolved by a real version gate plus a bash-version-independent probe that preserves path-normalization coverage on Linux, and every production change on the branch remains behavior-preserving and parse-clean under stock Bash 3.2.57.

Testing

Ran the targeted suites (fm-brief, fm-bootstrap, fm-x-mode, fm-lint) and then reproduced issue #1179 end-to-end on the actual stock macOS /bin/bash 3.2.57: at the base commit fm-brief.sh fails to parse and a real scaffold call exits 2 producing no brief, while at the target commit it parses clean and generates correct briefs for all three delivery modes with intact Definition-of-done text and no leaked heredoc markers; a full 3.2 parse sweep of the 92 bin scripts goes from 1 failure to 0. Only gap: fm-lint's two new sweep-behavior tests self-skip locally because pinned ShellCheck 0.11.0 is not installed, so that sweep's runtime rejection path is proven by CI's macOS lane rather than locally.

Evidence: macOS Bash 3.2 before/after CLI transcript for fm-brief.sh

$ /bin/bash --version | head -1 GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25) ### BEFORE - base commit fa0d85d on stock macOS /bin/bash 3.2 (issue #1179) $ /bin/bash -n bin/fm-brief.sh bin/fm-brief.sh: line 314: unexpected EOF while looking for matching )&#39;&#10;bin/fm-brief.sh: line 388: syntax error: unexpected end of file&#10;exit: 2&#10;&#10;$ /bin/bash bin/fm-brief.sh demo-task-1179 demo-project&#10;warn: no registry at $FM_HOME/data/projects.md; defaulting demo-project to no-mistakes off&#10;bin/fm-brief.sh: line 314: unexpected EOF while looking for matching )' exit: 2 $ ls $FM_HOME/data/demo-task-1179/ # no brief produced ### AFTER - target commit 2a4fefe, same shell $ /bin/bash -n bin/fm-brief.sh exit: 0 $ /bin/bash bin/fm-brief.sh demo-task-1179 demo-project scaffolded: $FM_HOME/data/demo-task-1179/brief.md (ship, mode=no-mistakes; replace {TASK}) exit: 0 # Definition of done The task is complete only when committed on your branch. When you believe it is complete, append done: {summary} to the status file and stop. Firstmate will then instruct you to run /no-mistakes to validate and ship a PR. $ grep -c EOF brief.md # no leaked heredoc marker 0 (direct-PR and local-only DOD branches also scaffold exit 0 with correct wording - see full file)

$ /bin/bash --version | head -1
GNU bash, version 3.2.57(1)-release (arm64-apple-darwin25)

### BEFORE - base commit fa0d85d on stock macOS /bin/bash 3.2 (issue #1179)
$ /bin/bash -n bin/fm-brief.sh
bin/fm-brief.sh: line 314: unexpected EOF while looking for matching `)'
bin/fm-brief.sh: line 388: syntax error: unexpected end of file
exit: 2

$ /bin/bash bin/fm-brief.sh demo-task-1179 demo-project
warn: no registry at $FM_HOME/data/projects.md; defaulting demo-project to no-mistakes off
bin/fm-brief.sh: line 314: unexpected EOF while looking for matching `)'
exit: 2
$ ls $FM_HOME/data/demo-task-1179/    # no brief produced

### AFTER - target commit 2a4fefe, same shell
$ /bin/bash -n bin/fm-brief.sh
exit: 0

$ /bin/bash bin/fm-brief.sh demo-task-1179 demo-project
warn: no registry at $FM_HOME/data/projects.md; defaulting demo-project to no-mistakes off
scaffolded: $FM_HOME/data/demo-task-1179/brief.md (ship, mode=no-mistakes; replace {TASK})
exit: 0

$ sed -n '/# Definition of done/,/^$/p' $FM_HOME/data/demo-task-1179/brief.md
# Definition of done
The task is complete only when committed on your branch.
When you believe it is complete, append `done: {summary}` to the status file and stop.
Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.

$ grep -c EOF brief.md    # no leaked heredoc marker
0

### direct-PR / local-only DOD branches on the same shell
$ /bin/bash bin/fm-brief.sh demo-direct-proj direct-proj
scaffolded: $FM_HOME/data/demo-direct-proj/brief.md (ship, mode=direct-PR; replace {TASK})
exit: 0
# Definition of done
This project ships **direct-PR**: you raise the PR yourself, without the no-mistakes pipeline.
The task is complete only when committed on your branch.
When it is implemented and committed, push your branch and open a PR with `gh-axi`, then append `done: PR {url}` to the status file and stop.
Do NOT run /no-mistakes. The configured merge authority decides whether to merge the PR; firstmate relays the outcome.
$ /bin/bash bin/fm-brief.sh demo-local-proj local-proj
scaffolded: $FM_HOME/data/demo-local-proj/brief.md (ship, mode=local-only; replace {TASK})
exit: 0
# Definition of done
This project ships **local-only**: no remote, no PR, no pipeline.
The task is complete only when committed on your branch `fm/demo-local-proj`. Do NOT push, do NOT open a PR, do NOT merge.
Keep your branch a clean fast-forward onto the current default branch - if `main` has advanced, rebase onto it so the eventual merge stays a fast-forward.
When it is implemented and committed, append `done: ready in branch fm/demo-local-proj` to the status file and stop.
The configured merge authority approves the ready branch, then firstmate merges it into local `main` through the guarded fast-forward path.
Evidence: Full bin set parse sweep under Bash 3.2 (base vs target)

## base commit fa0d85d FAIL: bin/fm-brief.sh bin/fm-brief.sh: line 314: unexpected EOF while looking for matching `)' bin/fm-brief.sh: line 388: syntax error: unexpected end of file checked 92 files, 1 fail to parse ## target commit 2a4fefe checked 92 files, 0 fail to parse

# Whole bin set under stock macOS /bin/bash 3.2 (the invariant fm-lint.sh now sweeps)

## base commit fa0d85d
FAIL: bin/fm-brief.sh
  bin/fm-brief.sh: line 314: unexpected EOF while looking for matching `)'
  bin/fm-brief.sh: line 388: syntax error: unexpected end of file
checked 92 files, 1 fail to parse

## target commit 2a4fefe
checked 92 files, 0 fail to parse
Evidence: Brief generated by fm-brief.sh under Bash 3.2 (target commit)
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of demo-project, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/demo-task-1179`
2. Run `no-mistakes doctor`; if it reports the repo is not initialized here, run `no-mistakes init`.

# Rules
1. Never push to the default branch. Never merge a PR.
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/var/folders/08/q_jqsxmj1hj793ylj_zbnljm0000gn/T/tmp.61cKWYKY1D/state/demo-task-1179.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
6. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   When firstmate replies or a blocker clears and you resume, append `resolved: {how it was decided or unblocked}` (add the same `[key=<slug>]` if you opened it with one) so the decision or blocker is durably closed and does not keep resurfacing.
7. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/Users/cweissteiner/.no-mistakes/worktrees/e0063c243f35/01KYMHAQSF5EG2ZMSZ0TSPF150/bin/fm-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/Users/cweissteiner/.no-mistakes/worktrees/e0063c243f35/01KYMHAQSF5EG2ZMSZ0TSPF150/bin/fm-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
The task is complete only when committed on your branch.
When you believe it is complete, append `done: {summary}` to the status file and stop.
Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.

You drive no-mistakes by responding to its gates, not by implementing fixes.
Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and `no-mistakes axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
Do not hand-edit, commit, or fix findings yourself while a run is active - the pipeline applies every fix.

Two firstmate-specific rules layer on top of that guidance:
- ask-user findings are never yours to answer: escalate to firstmate (rule 6) and stop.
  Firstmate applies the authority contract in its `AGENTS.md` and obtains any required captain decision.
  When the decision comes back, feed it to the gate with `no-mistakes axi respond` and let the pipeline apply it - do not route the question to "the user" or implement the fix yourself.
- Avoid `--yes`: it would silently bypass firstmate's authority check and any required captain escalation.

After /no-mistakes reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` and stop. You are finished.
- Outcome: ⚠️ 1 info across 1 run (7m15s)

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (3) ✅
  • ⚠️ bin/fm-brief.sh:220 - Same 3.2 parse trap remains reachable in this very file. HERDR_SECTION=$(cat &lt;&lt;&#39;EOF&#39; ... EOF\n) is prose captured through command substitution, and a quoted delimiter does not help - verified on GNU bash 3.2.57: a body containing it&#39;s inside X=$(cat &lt;&lt;&#39;EOF&#39; ...) fails with "unexpected EOF while looking for matching &#39;&#39;&#34; exactly like the unquoted form. Concrete path: an editor adds &#34;the crew&#39;s lab&#34; to the HERDR_SECTION prose, /bin/bash -n bin/fm-brief.shfails again, and fm-brief.sh is unusable on macOS - the identical user-facing failure the fix claims to close. The new CI loop catches it pre-merge, so this is a follow-up rather than a blocker, but the construct-level invariant the added comment asserts (&#34;Keep these blocks free of command substitution so prose can contain apostrophes&#34;) holds only for the three DOD blocks. Convert HERDR_SECTION toIFS= read -r -d '' HERDR_SECTION <<'EOF' || trueplus the same${VAR%$'\n'}` strip, keeping the quoted delimiter to preserve no-expansion semantics.
  • ℹ️ bin/fm-bootstrap.sh:691 - cadence_body=$(cat &lt;&lt;&#39;EOF&#39; ... ) is the same latent construct: its body is comment prose ("Non-X instances have no such file"), so any future apostrophe there makes bin/fm-bootstrap.sh unparseable under stock macOS Bash 3.2. Currently clean, and the new CI parse loop would catch a regression before merge. Same mechanical conversion as fm-brief.sh applies. (The four $(cat &lt;&lt;&#39;BASH&#39;|&lt;&lt;&#39;JQ&#39;) sites in bin/fm-fleet-snapshot.sh are code/filter bodies, lower risk of gaining prose apostrophes.)
  • ℹ️ .agents/skills/firstmate-coding-guidelines/SKILL.md:98 - The new Bash 3.2 parse rule is enforced only in the macos-stock-bash CI job, while the adjacent guideline line names bin/fm-lint.sh as "the single owner of the lint definition ... that CI and the no-mistakes pre-push gate both invoke". bin/fm-lint.sh contains no bash -n / BASH_VERSION check, so a macOS contributor - who has a real 3.2.57 at /bin/bash locally - gets a green pre-push gate and a red CI. The added SKILL.md text is honest about this ("which CI enforces per file"), so it is a deliberate-looking placement rather than a defect; moving a [ -x /bin/bash ] &amp;&amp; /bin/bash -n sweep into fm-lint.sh would make the pre-push gate the earliest boundary that holds the invariant.
  • ℹ️ bin/fm-brief.sh:281 - Issue macOS bash 3.2: fm-brief.sh fails to parse at fa0d85d (unexpected EOF, heredoc-in-case); watcher cycles also exit 1 without reason since update #1179 reports two symptoms; this branch fixes only the first. The second ("watcher cycles also exit 1 without reason since update") is not addressed: bin/fm-watch.sh does not invoke bin/fm-brief.sh (its only callers are fm-test-run.sh, fm-subagent-pretool-check.sh, fm-home-seed.sh), so the parse fix cannot explain it, and all bin scripts now parse clean under 3.2.57 - meaning the watcher exit is a distinct cause. Scoping this branch to the parse fix is reasonable; just do not auto-close macOS bash 3.2: fm-brief.sh fails to parse at fa0d85d (unexpected EOF, heredoc-in-case); watcher cycles also exit 1 without reason since update #1179 on merge.

🔧 Fix: convert remaining heredoc captures and own Bash 3.2 parse sweep in fm-lint
4 issues (1 warning, 3 infos) still open:

  • ⚠️ tests/fm-lint.test.sh:73 - The probe test writes a deliberately non-parsing script into the tracked source tree at $ROOT/bin/fm-lint-parse-probe.sh and relies solely on a happy-path rm -f at line 82. There is no cleanup trap: the file-scope trap &#39;exit 143&#39; HUP INT TERM at line 392 is inside the fake-shellcheck heredoc, not this test, and the repo's own convention (FM_TEST_CLEANUP_DIRS + trap fm_test_cleanup EXIT, used at lines 456-459) is not used here. Concrete failure: Ctrl-C, a lane timeout, or a kill between the write and the rm -f leaves bin/fm-lint-parse-probe.sh in the working tree, after which every bin/fm-lint.sh run fails (both the new parse sweep and shellcheck), the CI for script in bin/*.sh loop fails, and the junk file is a candidate for accidental commit - recovery is manual. Fix: tmp_dir=$(mktemp -d &#34;$ROOT/bin/.fm-lint-probe.XXXXXX&#34;) and put the probe inside it, then register it with the existing FM_TEST_CLEANUP_DIRS/EXIT-trap convention. That still exercises the sweep - the case pattern bin/*.sh matches across /, so bin/.fm-lint-probe.XXXX/probe.sh is swept - while the canonical bin/*.sh glob does not expand into a dot-prefixed subdirectory, so even a leaked probe cannot break lint or CI.
  • ℹ️ bin/fm-lint.sh:165 - The sweep filters roots by literal path spelling (case &#34;$path&#34; in bin/*.sh|bin/backends/*.sh)), so only relative bin/... arguments are parsed. bin/fm-lint.sh /repo/bin/fm-brief.sh or bin/fm-lint.sh ./bin/fm-brief.sh - both natural under the documented fm-lint.sh &lt;path&gt;... usage and both produced by shell tab-completion - silently skip the 3.2 check while the run still reports lint clean, so the owner states an invariant it did not test. Not reachable from the enforced paths (.no-mistakes.yaml line 37 and the CI run: bin/fm-lint.sh step both pass no arguments, giving the relative canonical set), which keeps this informational. Normalize before the case: strip a leading $ROOT/ and ./ from $path.
  • ℹ️ bin/fm-lint.sh:160 - The comment says the sweep is a "No-op where no real 3.2 exists" (and the header at line 19-21 says it "skips that sweep silently where /bin/bash is absent"), but the guard is [ -x /bin/bash ], which on Linux is Bash 5. There the loop runs to completion and passes without testing the 3.2 invariant at all - it is a tautology, not a no-op. The behaviour is exactly what was asked for; only the first comment overstates what the guard proves, which matters because the whole point of the change is that a green local gate should not imply a property it did not check. Reword to: no-op where /bin/bash is absent, and a real proof only where /bin/bash is the stock 3.2 shell - the macOS CI job remains the sole real 3.2 enforcement for Linux contributors.
  • ℹ️ CONTRIBUTING.md:48 - Doc drift inside the changed area: this line still scopes the owner's definition to "(the shellcheck file set, config, and pinned shellcheck version)", while .agents/skills/firstmate-coding-guidelines/SKILL.md:96 was updated in this commit to "(file set, config, pinned shellcheck version, and the stock Bash 3.2 parse sweep)". The new CONTRIBUTING.md line 97 states the sweep separately, so the two files no longer make the same statement about what fm-lint.sh owns. Add the parse sweep to the line 48 parenthetical.

🔧 Fix: harden lint parse sweep probe cleanup and path normalization
1 error still open:

  • 🚨 tests/fm-lint.test.sh:78 - test_parse_sweep_rejects_a_bash32_only_defect skips only when /bin/bash is missing, but its probe is a Bash-3.2-ONLY parse defect, so the test fails on every Linux lane. Chain: (1) the probe at lines 87-92 is BODY=$(cat &lt;&lt;EOF / the crew&#39;s lab / EOF ), which this branch's own tests/fm-brief.test.sh header documents as "Bash 5 later stopped mis-scanning it, while stock macOS Bash 3.2 still does" - that asymmetry is precisely why the macOS CI lane exists; (2) on ubuntu-latest /bin/bash is Bash 5, so [ -x /bin/bash ] at line 78 passes and does not skip; (3) the portable parallel shards install pinned ShellCheck 0.11.0 (.github/workflows/ci.yml lines 49-53 and 81-85), so pinned_ready at line 79 does not skip either; (4) the sweep therefore runs Bash 5, the probe parses fine, PARSE_RC stays 0, fm-lint.sh exits 0, and line 97 [ &#34;$rc&#34; -ne 0 ] || fail fires. Result: tests/fm-lint.test.sh goes red on the Linux portable test shards and on any Linux contributor's local no-mistakes Test gate, while passing on macOS - reproducing the exact green-on-my-machine / red-on-CI asymmetry this branch set out to remove, just with the platforms swapped. Fix: gate this test on /bin/bash actually being 3.x (e.g. case $(/bin/bash -c &#39;printf %s &#34;${BASH_VERSION%%.*}&#34;&#39;) in 3) ;; *) pass &#34;SKIP (/bin/bash is not stock Bash 3.2)&#34;; return 0 ;; esac), and add a second bash-version-independent probe (an unterminated if true; then fails to parse under every bash) carrying the three path-spelling assertions at lines 96-107. Without that second probe the review-2 path-normalization work in bin/fm-lint.sh gets zero coverage anywhere except macOS. Note: the assert_grep &#39;if [ -x /bin/bash ]; then&#39; at line 59 pins fm-lint.sh's own guard and is correct as-is - only this test's line-78 skip guard is wrong.

🔧 Fix: gate 3.2-only lint probe test on real Bash 3.2
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 info
  • ℹ️ tests/fm-lint.test.sh - tests/fm-lint.test.sh's two new Bash 3.2 parse-sweep behavior tests (parse sweep path-spelling check, Bash 3.2 parse sweep rejection) self-skip locally because pinned ShellCheck 0.11.0 is not resolvable on this machine; installing it would modify system state outside the worktree. The sweep's runtime behavior is therefore only proven by CI's macOS lane. The equivalent invariant was verified manually with /bin/bash -n over the full bin set.
  • ./tests/fm-brief.test.sh (15 ok, 0 fail)
  • ./tests/fm-bootstrap.test.sh (21 ok, 0 fail)
  • ./tests/fm-x-mode.test.sh (all ok; covers the fm-bootstrap x-mode cadence_body conversion)
  • ./tests/fm-lint.test.sh (all ok; 6 tests SKIP - ShellCheck 0.11.0 not resolvable locally)
  • Manual: /bin/bash -n bin/fm-brief.sh at base commit fa0d85d vs target 2a4fefe under real Bash 3.2.57
  • Manual: FM_HOME=&lt;tmp&gt; /bin/bash bin/fm-brief.sh demo-task-1179 demo-project at base (exit 2, no brief) vs target (exit 0, brief scaffolded)
  • Manual: /bin/bash bin/fm-brief.sh for direct-PR and local-only registry projects, inspecting rendered Definition-of-done sections
  • Manual: /bin/bash -n over bin/*.sh bin/backends/*.sh at both commits (92 files each)
⚠️ **Document** - 1 info
  • ℹ️ .github/workflows/ci.yml:308 - CI job name at .github/workflows/ci.yml:308 still reads "Stock macOS Bash snapshot compatibility", but the step now parse-checks every bin/.sh and bin/backends/.sh, not just the snapshot consumers. The step's inline comment and CONTRIBUTING.md:96 both describe the widened scope correctly, so no prose is stale; only the job label understates it. Left unchanged because the job name is workflow config, not documentation, and this phase may not alter executable config. Suggested follow-up: rename to "Stock macOS Bash 3.2 parse + snapshot compatibility".
⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 127)
✅ **Push** - passed

✅ No issues found.

@kunchenguid

kunchenguid commented Jul 30, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#1196 at 4cc3777d.

Rebased onto current main and reconciled with two changes that landed
in the meantime.

- kunchenguid#1093 already restored the fm-brief.sh heredoc captures on main, so
  this branch keeps only the comment that records why those blocks must
  stay free of command substitution (issue kunchenguid#166).
- kunchenguid#1282 replaced source assertions with behavioral coverage, so the
  source-shape assertions this branch carried are dropped. The parse
  sweep is proven by behavior instead: it rejects the exact 3.2-only
  construct, and it resolves a linted root however the path is spelled.
- fm-lint.sh now owns the parse sweep, so the pre-push gate - not only
  the CI macOS lane - is the earliest boundary that catches a 3.2 parse
  defect.
- The sweep covers every root in the owner inventory rather than the bin
  roots alone, because the CI macOS lane parses whatever --list-files
  reports. A tests/ root that only 3.2 rejects would otherwise pass the
  gate and fail that lane; a new test holds that parity.
- fm-bootstrap.sh converts its remaining heredoc capture to the same
  read -r -d '' form, so prose there can contain apostrophes.
@kunchenguid

kunchenguid commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Automated reminder: thanks for the PR! This branch currently has a merge conflict with the base branch.

When you get a chance, please rebase onto (or merge) the latest base branch, resolve the conflict, and push. After that, checks will re-run and the PR will get looked at again.

Noted for firstmate#1196 at 11f813a0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

macOS bash 3.2: fm-brief.sh fails to parse at fa0d85d (unexpected EOF, heredoc-in-case); watcher cycles also exit 1 without reason since update

2 participants