Skip to content

fix(bin): restore timed worker launches on Bash 3.2 - #32

Merged
rega10 merged 2 commits into
mainfrom
fm/firstmate-timeout-lib-bash32-bashpid
Sep 30, 2026
Merged

rega10 merged 2 commits into
mainfrom
fm/firstmate-timeout-lib-bash32-bashpid

Conversation

@rega10

@rega10 rega10 commented Sep 30, 2026

Copy link
Copy Markdown
Owner

Intent

Standing guidance from the user's global instructions: when you see a bug, lint failure, or flaky test, get it fixed along the way; bug fixes start by reproducing the bug as an end user would experience it.

Context needed to read this: the running Firstmate is the fork https://github.com/rega10/firstmate. The fork sync that landed as PR 31 (merge b90709f) brought in upstream change 8a18fe2 (upstream kunchenguid#5917), which added [ "$owner" != "$BASHPID" ] || owner=$PPID at bin/fm-timeout-lib.sh:224 inside fm_exec_timed. macOS /bin/bash 3.2 has no BASHPID, and Firstmate scripts run under set -u, so fm_exec_timed dies with "BASHPID: unbound variable". On the MacBook, where the only bash is /bin/bash 3.2, every worker launch now fails while moving the backlog item to In flight. That blocks the LocMon central deploy and a dashboard PR. Every other script in bin/ already guards the variable as ${BASHPID:-$$}. Reproduction on the mini: /bin/bash -c 'set -u; source bin/fm-timeout-lib.sh; fm_exec_timed 5 1 true' prints "bin/fm-timeout-lib.sh: line 224: BASHPID: unbound variable".

What Changed

  • Resolve the watchdog owner in Perl when the owner candidate matches the watchdog PID, avoiding the undefined BASHPID variable under Bash 3.2 with set -u.
  • Update the owner documentation and add coverage for Bash 3.2 command execution and owner death during watchdog startup.

Risk Assessment

✅ Low: The change is confined to Bash 3.2 compatible owner resolution in the Perl watchdog and behavioral regression coverage; the reviewed call paths preserve the existing owner and timeout behavior.

Testing

The baseline reproduced the reported Bash 3.2 failure. An initial targeted run exposed a test assertion that also used BASHPID; after fixing it, the timeout behavior tests passed. A disposable real backlog transition moved a task from Queued to In flight, with persisted state captured as evidence.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
Under stock Bash 3.2 with set -u, a bounded command runs and returns its output and exit status ✅ pass live Baseline commit produced BASHPID: unbound variable; the fixed command exited 0, and test_stock_bash32_runs_the_bounded_command passed.
A ship task's bounded dispatch transition moves its backlog row to In flight ✅ pass live Bash 3.2 backlog transition artifact: tasks-axi show reports Queued before dispatch and In flight afterward.
The watchdog still ends timed-out or orphaned commands and refuses an unbounded command ✅ pass live /bin/bash tests/fm-timeout-lib.test.sh exercised timeout, signal, owner-death, and refusal behavior with real processes.
Evidence: Bash 3.2 backlog transition

Source: Bash 3.2 backlog transition

/bin/bash 3.2, set -u, real fm_backlog_atomic_transition dispatch, real tasks-axi
BEFORE:
task:
  id: timeout-live
  title: Worker launch transition probe
  state: queued
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: "-"
  priority: "-"
  created: 2026-09-29
  closed: "-"
  deps: none
  links: none
  body: ""
AFTER:
task:
  id: timeout-live
  title: Worker launch transition probe
  state: in_flight
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: ship
  repo: "-"
  priority: "-"
  created: 2026-09-29
  closed: "-"
  deps: none
  links: none
  body: ""

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
Under stock Bash 3.2 with set -u, a bounded command runs and returns its output and exit status ✅ pass live Baseline commit produced BASHPID: unbound variable; the fixed command exited 0, and test_stock_bash32_runs_the_bounded_command passed.
A ship task's bounded dispatch transition moves its backlog row to In flight ✅ pass live Bash 3.2 backlog transition artifact: tasks-axi show reports Queued before dispatch and In flight afterward.
The watchdog still ends timed-out or orphaned commands and refuses an unbounded command ✅ pass live /bin/bash tests/fm-timeout-lib.test.sh exercised timeout, signal, owner-death, and refusal behavior with real processes.
  • /bin/bash -c 'set -u; eval "$(git show b90709f934cf1df09a6a1fb5519f26f639cc3de6:bin/fm-timeout-lib.sh)"; fm_exec_timed 5 1 true'
  • /bin/bash -c 'set -u; source bin/fm-timeout-lib.sh; fm_exec_timed 5 1 true'
  • /bin/bash tests/fm-timeout-lib.test.sh
  • Disposable worktree-local backlog: real fm_backlog_atomic_transition dispatch under /bin/bash 3.2, verified with tasks-axi show before and after
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Rene Garza Jr. added 2 commits September 29, 2026 22:53
…h 3.2

fm_exec_timed read $BASHPID unguarded to tell whether it was called from
the script itself (the exec replaces the script, so the owner is its
parent) or from a subshell (the script stays the owner). macOS /bin/bash
3.2 has no BASHPID, so under set -u every bounded call died with
"BASHPID: unbound variable", including the tasks-axi call that moves a
backlog item to In flight on every worker launch.

Neither ${BASHPID:-} nor ${BASHPID:-$$} keeps the owner correct under
bash 3.2. The empty default never matches, so a direct caller's owner
becomes the watchdog itself and a parent that dies during watchdog
startup goes undetected. The $$ default always matches, so a subshell
caller's owner becomes the script's parent instead of the script: a
script that dies during startup goes undetected, and the command is
ended when the script's parent exits even though the script lives.

The watchdog now makes the same decision from its own pid instead: after
the exec, perl's $$ is the pid of the shell that called fm_exec_timed,
so an owner equal to it means the caller was the script itself and the
owner resolves to the $PPID bash passes in. This is exact on every bash
version and unchanged under bash 5.

The timeout-lib tests now run both startup-owner cases, subshell and
direct caller, under stock /bin/bash 3.2 where it exists, and pin that a
set -u bash 3.2 caller runs the command and gets its status back.
@rega10
rega10 merged commit 7fe423c into main Sep 30, 2026
19 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.

1 participant