Skip to content

fix(fm-brief): scaffold ship briefs under bash 3.2 - #194

Closed
spuserswag wants to merge 1 commit into
kunchenguid:mainfrom
spuserswag:fm/fix-brief-bash-p9
Closed

spuserswag wants to merge 1 commit into
kunchenguid:mainfrom
spuserswag:fm/fix-brief-bash-p9

Conversation

@spuserswag

Copy link
Copy Markdown

Problem

bin/fm-brief.sh fails to run under macOS's stock bash 3.2 (/usr/bin/env bash resolves to /bin/bash, GNU bash 3.2.57) whenever it scaffolds a ship brief. Because the failure is at parse time, the script cannot scaffold any ship brief on a stock macOS box:

$ /bin/bash -n bin/fm-brief.sh
bin/fm-brief.sh: line 211: unexpected EOF while looking for matching quote
bin/fm-brief.sh: line 263: syntax error: unexpected end of file

Root cause

The ship-mode section built each mode's "Definition of done" with a heredoc wrapped in command substitution:

DOD=$(cat <<EOF
...
Follow no-mistakes' own guidance ...
... no-mistakes axi respond ...
EOF
)

The no-mistakes DOD body contains apostrophes (no-mistakes'). Bash 3.2 has a well-known parser bug where a single quote inside a heredoc body that is itself nested in $(...) command substitution is treated as an opening quote, so the parser runs to EOF looking for its match and aborts. Modern bash (5.x) parses it fine, which is why it was never caught. The scout path was unaffected because it uses a plain cat > "$BRIEF" <<EOF (no command substitution).

Fix

Fix the structural pattern, not the wording (the apostrophes are intentional and future DOD/Setup bodies could gain more): drop the DOD variable and the command substitution, and append each mode's Definition of done directly into the brief with a plain cat >> "$BRIEF" <<EOF - the same non-nested heredoc form the scout path already uses. The pre-write case now only prepares the mode-specific Setup/Rule-1 text. Any future apostrophe in a DOD/Setup body is now safe.

The shebang is untouched (#!/usr/bin/env bash); no hard-coded bash path.

Byte-for-byte identical output

The generated brief is unchanged from the previous intended output on modern bash. Command substitution stripped the heredoc's single trailing newline before $DOD expanded; the common body now ends at the "Project memory" blank line and each DOD is appended exactly where $DOD used to expand, reproducing the same join and the same single trailing newline. DOD body text diffs identical to HEAD for all three ship modes.

Verification

Before (on macOS, /bin/bash = 3.2.57):

$ /bin/bash -n bin/fm-brief.sh
bin/fm-brief.sh: line 211: unexpected EOF while looking for matching quote

After:

$ /bin/bash -n bin/fm-brief.sh   # exits 0, no output

Generated real briefs under bash 3.2.57 for no-mistakes, direct-PR, local-only, and scout, plus the secondmate charter - all scaffold with no syntax error and correct content (apostrophes render intact). Confirmed the join point keeps a single blank-line separator and the file ends in exactly one trailing newline (no double newline).

Scope

Tightly scoped to fm-brief.sh. bin/fm-bootstrap.sh has the same $(cat <<EOF) shape, but its heredoc bodies carry no lone apostrophes, so it parses cleanly under bash 3.2 (/bin/bash -n bin/fm-bootstrap.sh -> OK) and is left unchanged.

The ship-mode section built each mode's "Definition of done" text with a
heredoc wrapped in command substitution (DOD=$(cat <<EOF ... EOF)). The
no-mistakes DOD body contains apostrophes ("no-mistakes' own guidance",
"no-mistakes' respond"). Bash 3.2 (macOS stock /bin/bash) mis-parses a
single quote inside a heredoc body that is itself nested in $(...): it
treats the apostrophe as an opening quote and runs to EOF looking for the
match, aborting the whole script with:

  fm-brief.sh: line 211: unexpected EOF while looking for matching `''

Modern bash (5.x) parses it fine, so it was missed. Because the failure is
at parse time, fm-brief.sh could not scaffold ANY ship brief on a stock
macOS box, not just no-mistakes.

Fix the structural pattern rather than the wording: drop the DOD variable
and the command substitution, and append each mode's Definition of done
directly into the brief with a plain `cat >> "$BRIEF" <<EOF` (the same
non-nested heredoc form the scout path already uses). The pre-write case
now only prepares the mode-specific Setup/Rule-1 text. Any future
apostrophe in a DOD/Setup body is now safe.

The generated brief is byte-for-byte identical to the previous intended
output on modern bash: the common body ends with the "Project memory"
blank line and each DOD is concatenated exactly where $DOD used to expand
(command substitution stripped the heredoc's single trailing newline, and
the direct append reproduces that join).

Verified under bash 3.2.57 (/bin/bash) that all four delivery paths
(no-mistakes, direct-PR, local-only, scout) plus the secondmate charter
scaffold with no syntax error and correct content; DOD body text diffs
identical to HEAD for all three ship modes.

fm-bootstrap.sh has the same $(cat <<EOF) shape but its heredoc bodies
carry no lone apostrophes, so it parses fine under bash 3.2 and is left
unchanged.
@kunchenguid

Copy link
Copy Markdown
Owner

thanks for this. quick note on process - i require PRs to come through no-mistakes, which is why the "PR must be raised via no-mistakes" check is red here (this was opened directly).

the reason i ask for it: no-mistakes runs review, tests, lint, and docs on your branch before the PR is opened, so contributions arrive already validated. that keeps the quality bar high and keeps me from having to hand-review every change - i maintain this solo, so it's the only way i keep up.

can you re-raise it through no-mistakes? running the pipeline on your branch opens/updates the PR through the gate and turns that check green. the change looks useful, it just needs to come through that path. ping me if the setup gives you trouble.

@nikzorz

nikzorz commented Aug 25, 2026

Copy link
Copy Markdown

Measured, then declined. Closing with the numbers rather than a bare close.

Six sweep cells of 80 rounds each across easy/normal/hard, both arms off one build behind a
runtime flag, plus a contact pass of 80 fresh openings per cell. Statistics are two-sided permutation
tests over 20,000 relabellings for medians, two-sided Fisher exact for the cap-round share.

The premise does not hold on the shipped map. The REACH_POI arm's own comment reasons from a
~1288px approach. That is the distance to the anchor, and bots never walk it: both teams walk at
each other and close at ~360px/s combined, so first contact lands at tick 109 on easy and tick 27
on normal. The unseen advance is 1.8s / 0.45s long, not a long approach.

What the dash buys on that advance: 0.087s (easy, p=0.0001) and 0.018s (normal).

What it costs is exact and total. Every bot that would have reached its first enemy holding a
mobility Ability reaches it without one: 112/640 -> 0/640 on easy, 36/640 -> 0/640 on
normal.

On hard it is a strict no-op - 80 of 80 rounds byte-identical on every counter. Structural, not
statistical: hard.tres authors an unlimited vision radius, so an ADVANCE candidate is never built
while an enemy lives.

On easy it is a different feature from the one proposed. It fires 5.9x more dashes than an
opening approach can physically spend, because easy.tres's 1-second target memory makes ADVANCE a
recurring mid-round state rather than an opening one. Measured consequence: DISENGAGE dashes
3185 -> 1878 (-41%), and retreat evaluations holding a ready escape fall 41.2% -> 33.7%.

On the #190 stall axis the instrument returns NO verdict, and that is not the same as a negative.
Cap-decided rounds move 30/80 -> 36/80 on easy and 8/80 -> 11/80 on normal, pooled 38/160 -> 47/160
at p = 0.31. Every point estimate points the wrong way; none separates from chance. Separating the
pooled effect at 80% power needs ~970 rounds per arm against the 160 taken, roughly 12x this pass.
That was judged not worth paying for a change whose measured benefit is under a tenth of a second.

Not measured: the Objective producer of REACH_POI (test_arena authors only SPAWN anchors), and
anything under arena_lives, which is what the host lobby actually opens on.

What would reopen this: a map whose spawn anchors are genuinely a long walk apart, or a profile
with a short vision radius and a long target memory. No shipped map or profile is that shape today.

A duration-gated variant was sketched (threshold in ticks on how long the bot has been advancing,
authored per profile, with the Objective producer still refusing so OBJECTIVE_HAUL_WEIGHT's property
survives). It was considered and declined as part of this decision, not deferred. It is more than one
line and would need its own sweep.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: scheduled 7:10pm PT 8/24 pass (FM-FMOSS-CRON). First look on current main 038d0f7ec6ba7238a151722931434dcf06ff37c4 (#2942). VISION.md read in full. Thank you @spuserswag.

class=corrective leftover. The bash 3.2 ship-brief parse failure this PR names is already on main via #1093 (fix: restore stock macOS Bash 3.2 brief scaffolding, merged 2026-07-28).

Verified on THIS main bin/fm-brief.sh, not from metadata: there is no DOD=$(cat <<EOF and no $(cat << command-substitution heredoc. Ship DOD blocks use IFS= read -r -d '' DOD <<EOF || true (the #1093 structural fix), then expand $DOD. That is the same defect class (apostrophe inside a heredoc nested in $(...)) with a different, later implementation than this PR's cat >> "$BRIEF" <<EOF append.

This DIFF vs main: 377 commits behind; one file bin/fm-brief.sh against a July tree that still had DOD=$(cat <<EOF. Applying it now would fight the #1093 read -d '' form and later brief work (#2838 {TASK} gate, #2856, #2764). Not a competing remaining gap. Closing rather than opening another PR.

VISION.md: closing aligns. The macOS parse hole is already closed on main. Authority/scripts/restart/spine/vendor/scope are n/a for a leftover close. Scripts-own-the-mechanics is already satisfied by the landed structural guard (and CI stock-macOS bash job from #1093).

This HEAD: dc5d94608015529c8aba2c4ec669a63d6d6476a5. UNKNOWN merge state, ahead 1 / behind 377. No no-mistakes-pipeline-attestation:v1 (opened directly; NM check red — captain already asked 2026-07-03 to re-raise via no-mistakes). CI from July: Lint/Behavior/Repo invariants SUCCESS, NM FAILURE. Workflows already ran; not first-time for a re-approval. No security issue.

The later nikzorz comment on this thread is unrelated (game-bot dash measurements) and is not firstmate evidence.

Land-eligible rec: NO (already on main via #1093). Captain-flag NOW: no. Closing as leftover of #1093.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: closing as already fixed on main by #1093.

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate: first look on current main 038d0f7ec6ba (#2942). Thank you, spuserswag.

class=corrective leftover. The bash 3.2 abort (DOD=$(cat <<EOF with apostrophes inside $(...)) is already gone on THIS main. Current bin/fm-brief.sh builds each mode's Definition of done with IFS= read -r -d '' DOD <<EOF and then DOD=${DOD%$'\n'} — the comment on main names the removed command-substitution parse. Delivery-contract lines and later brief text have also moved on (this branch is 377 behind and CONFLICTING).

This DIFF restates the same three mode texts via cat >> "$BRIEF" on an ancient fm-brief.sh. Not a remaining path gap. No competing PR opened.

The 2026-08-25 nikzorz comment on this thread is off-topic (game-map dash sweep) and is not a reason to close. Closing because main already has the scaffold fix.

VISION.md: closing aligns. Authority/scripts/restart/spine/vendor/scope are n/a for a leftover close.

This HEAD: dc5d94608015529c8aba2c4ec669a63d6d6476a5. CONFLICTING / DIRTY, ahead 1 / behind 377. No no-mistakes-pipeline-attestation:v1. July CI ran; Require no-mistakes failed (opened outside the pipeline). First-time author; CI not re-approved (closing).

Land-eligible rec: NO. Captain-flag NOW: no.

Closing as already fixed on main. This is not waiting-on-author and not a captain-decision.

@kunchenguid kunchenguid mentioned this pull request Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants