Skip to content

macOS: bash 3.2, BSD tools, BT.709 tags with ffmpeg 7.1 and newer, LF checkouts (fixes #2) - #6

Merged
Leonxlnx merged 5 commits into
mainfrom
fix/macos-2
Oct 18, 2026
Merged

Leonxlnx merged 5 commits into
mainfrom
fix/macos-2

Conversation

@Leonxlnx

Copy link
Copy Markdown
Owner

Fixes #2

Five commits on main (bee5d78): the fix (d2532b3), the README's script count (080eec9), LF checkouts
(bb26a14), hf-preflight.cjs --help and a safer script refresh for existing projects (d27fab7), and CI that tests
the fix on a real macOS runner (ea2e007).

Thanks to @gdorea for the precise report: all three problems were real, and the suggested fixes were right. This PR
applies them and the same fixes in the places the report did not reach.

The fixes from #2

  1. bash 3.2 cannot parse hf-finish.sh. When bash 3.2 looks for the closing paren of a $( ), it scans the
    text as shell code, heredoc bodies included. The quote characters in the preflight's JavaScript regexes (["'])
    then open strings that never close. The preflight moves, verbatim, into scripts/hf-preflight.cjs, and
    hf-finish.sh runs node "$SCRIPT_DIR/hf-preflight.cjs". The three other heredocs inside $( ) (deliver.sh
    ×2, render.sh ×1) parse today only because their Python happens to have balanced quotes. They become shell
    functions called inside $( ).
  2. BSD wc pads its count. hf-finish.sh trims the count (tr -d '[:space:]') and compares it with -eq.
  3. BT.709 tags with ffmpeg 7.1 and newer. ffmpeg 7.1 and newer take the stream's colour tags from the frames, and
    those win over -color_primaries / -color_trc. scale sets only the matrix and range on the frames, so the
    HyperFrames encode came out bt709/None/None/tv. hf-finish.sh now ends its -vf with
    setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv.
    • The same bug, worse, in accumulate.py (the encoder of every --blur master, for Remotion and
      HyperFrames). Its raw yuv pipe has no colour labels at all, so ffmpeg 7.1+ inserts its own scaler. That scaler
      converts the planes from an assumed matrix to BT.709, so the colours shift (lossless test: PSNR Y 22.5 dB against
      the source), and the file still has no primaries or transfer. -vf setparams=... labels the planes instead: a
      lossless encode now decodes to exactly the source planes.
    • deliver.sh --webm gets the same -vf setparams=... (it only labels; the master is BT.709 limited range).
      Without it, a master that lacks the tags passes that on to the WebM.

Smaller macOS fixes from the same pass:

  • hf-finish.sh strips colour codes from the HyperFrames logs with an ESC character built by printf. BSD sed has
    no \x1b, so before this the codes stayed in.
  • render-chunks.sh falls back to sysctl -n hw.ncpu when there is no nproc.
  • new-film.sh matches motion.js with [[:space:]] instead of \s, which is not portable to BSD grep.
  • docs/setup.md loses a stale troubleshooting row about date %N. render.sh only uses date +%s and times the
    sharp pass with python3.

Later commits

  • LF checkouts (.gitattributes). With core.autocrlf=true (the Git for Windows default) the scripts were
    checked out with CRLF, and WSL's bash then stops at set: pipefail: invalid option name. Every text file now checks
    out with LF, scripts are always text, and media files are marked binary. The index already held LF everywhere, so
    no blob changes.
  • hf-preflight.cjs --help. Run on its own, the helper crashed with a TypeError. It now prints a pointer to
    hf-finish.sh --help and exits 2. SKILL.md, the README and docs/setup.md name it, with brand-svg.ts, as the
    scripts without a --help of their own.
  • Refreshing an existing project's scripts. The first version of the troubleshooting rows said
    cp -R "$SKILL/scripts/." scripts/, which also copies the skill's own tests (fixtures that break the rules on
    purpose) and Python caches into the project. A new docs/setup.md section, "Updating a project's scripts", gives
    the same tar copy that new-film.sh uses, and both rows link to it.
  • CI on macOS and a better heredoc guard (details below).

Changes

File Change
skills/cinetic/scripts/hf-preflight.cjs new: the preflight, byte-identical body plus a header comment and the --help / missing-environment answer (exit 2). It is .cjs because it uses require() and a project's package.json could declare "type": "module"
skills/cinetic/scripts/hf-finish.sh calls the preflight file and says clearly if the file is missing; wc fix; ESC for sed; setparams in the encode
skills/cinetic/scripts/deliver.sh probe_master and poster_match functions; setparams in the WebM
skills/cinetic/scripts/render.sh subframe_span function
skills/cinetic/scripts/accumulate.py -vf setparams=... on the encoder; the comment that said "no scale filter, so no hidden dither" (not true on new ffmpeg) now says what happens and why
skills/cinetic/scripts/render-chunks.sh sysctl fallback
skills/cinetic/scripts/new-film.sh [[:space:]] instead of \s
skills/cinetic/scripts/probe.py the bt709 failure hint mentions setparams
skills/cinetic/scripts/test/encode-tags.test.py new. Static part: every encode that writes -color_primaries bt709 also has setparams. With ffmpeg: the hf-finish.sh filter string (read from the script) and accumulate.py (8-bit and 10-bit) write bt709/bt709/bt709/tv
skills/cinetic/scripts/test/bash32.test.py new: a multi-line scan for a heredoc inside $( ), backticks, <( ) or >( ) (it follows quotes, comments, heredoc bodies, arithmetic and nesting), with self-tests. It replaces a one-line git grep that missed X=$( with the heredoc on the next line, and nested parens
skills/cinetic/scripts/test/scripts-docs.test.mjs new: every script but the two internal helpers answers --help; hf-preflight.cjs exits 2 with its pointer; the docs name the same helpers; docs/setup.md refreshes scripts with new-film.sh's own copy
.gitattributes new: LF checkouts for text, binaries marked
.github/workflows/ci.yml check job additions; new bash32, macos and encode-tags jobs (below)
references/finishing.md §4 the recipe gains setparams and a paragraph on why it is needed
skills/cinetic/SKILL.md names hf-preflight.cjs with brand-svg.ts as the scripts without --help
docs/setup.md the ffmpeg note and the CI line; troubleshooting rows for the two macOS symptoms and the bt709/None/None/tv probe failure; "Updating a project's scripts"; the stale row removed
README.md, CHANGELOG.md the scripts row (28 scripts; hf-preflight.cjs is an internal helper without --help), the new tests in Contributing, a description of the bash 3.2 guard; [Unreleased] Fixed and Added

CI

The macos job has never run on GitHub: this PR's first CI run is its first run. The bash32 and
encode-tags jobs have not run on GitHub either. Their steps were run locally (see Verification).

  • macOS (bash 3.2, BSD tools, current ffmpeg) job (macos-latest): the platform of macOS: hf-finish.sh fails with stock bash 3.2, frame-count check fails with BSD wc, bt709 probe fails with ffmpeg 7.1 #2 for real.
    • Fails if /bin/bash is no longer 3.2, and warns if Homebrew's ffmpeg is older than 7.1.
    • /bin/bash -n on every tracked .sh.
    • The HyperFrames preflight (hf-finish.sh <starter> --export-cues, the same as npm run cues) under /bin/bash.
    • new-film.sh --engine hyperframes scaffolds a project (and must not copy scripts/test), then the refresh
      command from docs/setup.md runs with BSD tar and the project's hf-finish.sh exports cues.
    • encode-tags.test.py on Homebrew's ffmpeg (7.1 or newer, where the setparams fix matters).
  • bash 3.2 (macOS) job (Docker image bash:3.2.57, on Ubuntu, seconds to run):
    • bash32.test.py over every tracked .sh; it names each heredoc it finds and the fix;
    • bash -n on every tracked .sh, run by bash 3.2;
    • hf-finish.sh <starter> --export-cues run by bash 3.2 (node from Alpine's apk). It must get through the
      preflight and write valid cues. A fresh checkout has no starter fonts or vendor/gsap.min.js, so a preflight
      that gets through still exits 1 after writing the cues, and the step accepts that. A script bash 3.2 cannot parse
      writes nothing.
  • BT.709 encode tags job: installs ffmpeg and python3-numpy and runs encode-tags.test.py. Ubuntu's ffmpeg
    is 6.1, which passes the encodes with or without setparams, so here the static part is the real guard; the macOS
    job runs the encode part on a current ffmpeg.
  • check job: node --check for scripts/**/*.mjs|cjs (the .py files already get py_compile), runs
    scripts-docs.test.mjs, and checks line endings: LF in the index, LF checkout attributes for every script type,
    and binary attributes for media.

The container job pins the exact bash version and is fast; the macOS job also covers the BSD userland and a current
ffmpeg, which the container cannot.

Verification

Local: Windows 11 with Git Bash 5.2.37 and ffmpeg 8.0.1; WSL Ubuntu 24.04 with bash 5.2.21, node 22.23 and ffmpeg
6.1.1. No Mac and no bash 3.2 binary were available.

  • Preflight unchanged. I ran hf-finish.sh <starter> --export-cues before and after the change, on the fresh
    starter (fonts missing, exit 1) and on a copy with stub font and vendor files (exit 0). The cues JSON, stderr and
    exit codes are identical, and so is the P_... line from running the preflight on its own. The CI step, simulated
    with WSL bash, prints 300 frames, 7 events, and its cues are byte-identical to the pre-change run.
  • Heredocs inside $( ). bash32.test.py on main's scripts reports all four: hf-finish.sh:82 (the reported
    failure), deliver.sh:105, deliver.sh:156 and render.sh:219. On this branch it reports none, and its 18
    self-tests pass: 8 cases it must flag (the macOS: hf-finish.sh fails with stock bash 3.2, frame-count check fails with BSD wc, bt709 probe fails with ffmpeg 7.1 #2 case, $( with the heredoc on the next line, nested $( ),
    backticks, <( ), >( ), $( ) inside ${...}, two heredocs in one substitution) and 10 it must not (a
    top-level heredoc, the fix itself, a here-string, << as a shift or inside quotes or comments, $#, and others).
    bash -n passes on every script with bash 5.2 (Git Bash and WSL). Whether bash 3.2 itself accepts the scripts is
    shown by the CI jobs.
  • Encode tags, ffmpeg 8.0.1 (Windows):
    • hf-finish.sh encode line on testsrc2 PNGs: before bt709/None/None/tv, after bt709/bt709/bt709/tv.
    • accumulate.py 8-bit and 10-bit: before bt709/None/None/tv, after bt709/bt709/bt709/tv. probe.py fails the
      old files and passes the new ones.
    • accumulate.py's encoder arguments on raw yuv420p with -qp 0: before, the decoded planes differ from the
      source (auto-inserted scaler, PSNR Y 22.5 dB); after, they are identical.
    • WebM from a master missing the tags: before bt709/None/None/tv, after bt709/bt709/bt709/tv. From a fully
      tagged master, the WebM decodes to the same md5 with or without setparams.
    • encode-tags.test.py on the branch head: all pass. On main's scripts it fails 6 checks and exits 1.
  • ffmpeg 6.1.1 (WSL): the old and new commands give identical tags, and accumulate.py's output decodes to
    identical md5 before and after (8-bit and 10-bit). setparams changes nothing on older ffmpeg.
  • deliver.sh smoke test (WSL) on a tiny master with --poster-from and --webm: probe_master,
    poster_match (its message and exit status reach MATCH) and the tagged WebM all work.
  • Branch head: scripts-docs.test.mjs 16 of 16 checks pass; hf-preflight.cjs --help prints its pointer and
    exits 2; lint-film.test.mjs and pick.test.py pass (WSL). py_compile of every script and node --check of the
    .mjs / .cjs: ok. The workflow YAML parses (jobs check, bash32, macos, encode-tags). The bash32 job's
    preflight step, run with WSL bash in place of the container, passes; its scan step is bash32.test.py (above).

Notes for review

  • Existing projects keep their old scripts. new-film.sh copies scripts/ into each project, so a film made
    with 1.0.0 keeps the broken hf-finish.sh. The troubleshooting rows link to "Updating a project's scripts" in
    docs/setup.md, which refreshes them with the scaffold's own tar copy (without the skill's tests).
  • Not verified on a real Mac yet. That is what the new macos job is for. The BSD wc / sed fixes follow the
    report and were tested with GNU tools on the same inputs.
  • Remotion's own encodes are unchanged: render.sh, render-chunks.sh, deliver.sh --alpha and --variants
    use Remotion's bundled ffmpeg with --color-space=bt709. If that ffmpeg is 7.1 or newer, they may show the same
    missing tags. render.sh runs probe.py on every master, so that would fail loudly. Worth checking on the next
    real render.
  • Merging with next: a test merge (git merge-tree) conflicts only in CHANGELOG.md ([Unreleased]) and
    README.md, because both sides added lines; keep both. In the README, the Contributing test list gets both sides'
    tests, and the scripts row becomes 29 scripts (next adds track-info.py, this PR adds hf-preflight.cjs).

🤖 Generated with Claude Code

Leonxlnx and others added 5 commits October 17, 2026 09:47
Fixes #2.

- hf-finish.sh: the preflight moves out of a heredoc inside $( ), which
  bash 3.2 (macOS's /bin/bash) scans as shell code, into
  scripts/hf-preflight.cjs, verbatim (same cues, messages and exit codes).
  The other heredocs inside $( ), in deliver.sh and render.sh, become
  shell functions called inside $( ).
- hf-finish.sh: BSD wc's padded frame count is trimmed and compared as a
  number; colour codes are stripped with an ESC from printf (BSD sed has
  no \x1b). render-chunks.sh falls back to sysctl -n hw.ncpu.
- BT.709: ffmpeg 7.1 and newer take the stream's colour tags from the
  frames, over -color_primaries/-color_trc. The hf-finish.sh encode,
  accumulate.py and the WebM in deliver.sh end their filters with
  setparams. accumulate.py's unlabelled raw pipe was also converted by an
  auto-inserted scaler (colours shifted); it now passes through bit-exact.
  With ffmpeg 6.1 the output is identical to before.
- CI: a bash 3.2.57 job (bash -n of every script, the HyperFrames preflight
  under 3.2, a guard against heredocs inside $( )), node --check of the
  Node scripts, and a job that runs the new scripts/test/encode-tags.test.py.
- Docs: finishing.md section 4, setup.md (requirements, troubleshooting,
  the stale date %N row removed), README contributing, CHANGELOG.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The scripts row says 28 scripts now, names hf-preflight.cjs next to
brand-svg.ts as an internal helper without --help, and lists the
encode-tags test in scripts/test/. Part of #2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
core.autocrlf=true (the Git for Windows default) checked the scripts out with
CRLF, and WSL's bash then stops at "set: pipefail: invalid option name". The
index already holds LF everywhere, so renormalising changes no blob.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…affold

- hf-preflight.cjs crashed with a TypeError on --help or without its
  environment; it now points at hf-finish.sh --help and exits 2.
  SKILL.md names it with brand-svg.ts as the scripts without --help.
- docs/setup.md told projects to `cp -R "$SKILL/scripts/." scripts/`, which
  also copies the skill's tests (fixtures that break the rules on purpose)
  and caches. Both troubleshooting rows now link a new "Updating a project's
  scripts" section with new-film.sh's own tar copy.
- scripts/test/scripts-docs.test.mjs: the --help contract, hf-preflight's
  exit 2, the docs naming the same helpers, and the refresh command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- macos-latest job: the real /bin/bash 3.2 parses every .sh and runs the
  HyperFrames preflight; new-film.sh scaffolds a HyperFrames project and the
  docs' refresh command runs with BSD sed and tar; encode-tags.test.py runs
  on Homebrew's ffmpeg (7.1+, where the setparams fix matters). Ubuntu's
  ffmpeg 6.1 passes the encodes with or without it.
- The one-line grep for a heredoc inside $( ) missed `X=$(` followed by a
  heredoc on the next line and nested parens. scripts/test/bash32.test.py
  scans across lines (quotes, comments, heredoc bodies, arithmetic, nesting
  of $( ), backticks, <( ), >( )), with self-tests; it flags all four
  heredocs main had inside $( ).
- check job: runs scripts-docs.test.mjs, and checks LF in the index, LF
  checkout attributes for scripts and binary attributes for media.
- new-film.sh matches motion.js with [[:space:]] instead of \s.
- CHANGELOG and README describe the guard as it is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-17T22:10:26.626786Z ea2e007 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@Leonxlnx
Leonxlnx merged commit f1b97b3 into main Oct 18, 2026
8 checks passed
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.

macOS: hf-finish.sh fails with stock bash 3.2, frame-count check fails with BSD wc, bt709 probe fails with ffmpeg 7.1

1 participant