fix(bin): deterministically order remote tool paths - #2870
Conversation
fm_remote_job_compose_operator_path built the asdf and mise install directories with `compgen -G`, which does not sort. Bash sorts glob matches in pathexp.c, on the shell's own pathname-expansion path only; `compgen -G` reaches the same glob_filename through pcomplete.c, which sorts nothing. On bash 3.2 (macOS /bin/bash) and every bash before 5.3 that handed the composition raw readdir order, so which install of a multi-version tool a remote job resolved was decided by directory order on disk rather than by this composition. Expand the globs at the call sites and let the function take the matches, so the composition and the documented portable-PATH contract are the same operation. Quoting the account home at the call site also stops a home whose name contains glob metacharacters from being reinterpreted. The colocated regression pins both the order and the mechanism: bash 5.3 moved sorting into the glob library, so an order-only assertion cannot see the defect there.
Confidence Score: 5/5The PR appears safe to merge with no actionable defects identified. The changed helper preserves unmatched-glob filtering and PATH deduplication, all repository call sites use the new contract, and focused coverage verifies deterministic install ordering. Reviews (1): Last reviewed commit: "no-mistakes(review): Remove source-readi..." | Re-trigger Greptile |
|
Speaking as Kun's firstmate: Scheduled 11:10am PT 8/23 pass. VISION.md read in full from current main VISION (inspected Class: corrective. Auto-eligible class. Not default-behavior: production was required to match the existing sorted contract; locale-identical hosts already agreed. Security: none. Quoting the account home at the call site also stops glob-metacharacter reinterpretation of the home path. No workflow-file / secret / injection risk. Overlap / HOLD: none of the standing spawn/teardown/herdr/pool mechanism holds. Incidental same-file overlap with #2586 (teardown rewrite hold) on CI / NM: HEAD Workflows: already approved (CI completed SUCCESS on this HEAD). Run IDs: 32646112872 (CI), 32646112875 / 32646981593 (Require no-mistakes). No pending first-time-fork approval. Land-eligible rec: YES (corrective; matching NM attestation; green CI including macOS Bash snapshot; CLEAN despite behind 3). Captain-flag NOW: no. |
|
The diff is clean and the triage reads correctly to me. What I see in the code:
No issues from my side. Aligns with triage: corrective, no new layer, quoting fix is a real second bug closed for free. |
|
Speaking as Kun's firstmate: this is merged. Thank you @karotkriss — really appreciate you taking the time on this. |
Fixes #2843
Intent
Fix GitHub issue #2843: fm-on.sh's composed child PATH does not match the sorted portable-PATH contract tests/fm-on.test.sh expects. The reporter saw tests/fm-on.test.sh fail identically on pristine main on macOS: the child PATH a real fm-on remote dispatch composes grouped the mise/asdf install directories in filesystem-readdir order, while the documented portable-PATH contract - and the test's from-scratch reconstruction of it, which uses a literal bash glob - produce sorted order. The issue did not identify the composition site and asked whether the discrepancy is cosmetic or a real tool-version-resolution defect.
Root cause traced and reproduced: bin/fm-remote-job-lib.sh's fm_remote_job_compose_operator_path is the single composition owner for both the SSH entrypoint and the long-lived remote job worker, and it built the asdf and mise install directories through fm_remote_job_append_glob_dirs, which used
compgen -G. Bash sorts glob matches only on the shell's own pathname-expansion path (pathexp.c calls strvec_sort after glob_filename);compgen -Greaches the same glob_filename through pcomplete.c, which sorts nothing. Verified in the bash 3.2 source (macOS /bin/bash) and reproduced live against a locally built bash 5.2:compgen -Greturned raw readdir order while a literal shell glob on the identical pattern returned sorted order. Bash 5.3 moved sorting into the glob library, which is why the defect is invisible on a modern Linux dev box and on CI.This was answered as version-resolution-relevant, not cosmetic: the mise/asdf path deliberately appends every install directory of every tool with no version selection of its own (unlike the nvm path, which has explicit selection), so PATH order alone decides which node/bun/python a remote job runs. Readdir order made that choice depend on the filesystem and change whenever directories are added or removed.
Fix decision: rather than adding a sort next to compgen, make the composition use the same mechanism the contract is written in. fm_remote_job_append_glob_dirs was renamed to fm_remote_job_append_dirs and now takes already-expanded matches, with the three call sites expanding the globs themselves with the account home quoted. The composition and the documented contract are now literally the same operation and cannot drift again on any bash, in any locale. Renaming rather than keeping the old name was deliberate: a caller passing a quoted pattern to the new signature would be silently dropped, so the name change makes the new contract explicit. Quoting the account home at the call site also fixes a latent second bug - a home directory whose name contains glob metacharacters was previously reinterpreted by compgen.
Regression coverage was deliberately placed in tests/fm-remote-job.test.sh, the colocated test of the composition owner, rather than in tests/fm-on.test.sh as the original triage suggested: fm-on.test.sh asserts the end-to-end contract against the real account home, so it can only exercise the glob path on a host that actually has mise or asdf installs and cannot be made deterministic. tests/fm-on.test.sh is deliberately left untouched and was NOT adjusted to accept readdir order. The new case asserts that the composed order equals the shell's own pathname expansion for a fixture home created in a deliberately unsorted order. Verified: that assertion fails on pre-fix code under bash 3.2 and bash 5.2, and passes on the fix.
Honest limitation, worth knowing before reading CI as proof: bash 5.3 moved glob sorting into the glob library, so on bash 5.3 and later
compgen -Gand shell pathname expansion agree and this assertion reads green even against the unfixed code. It detects the defect only where the defect exists - bash 3.2 (macOS /bin/bash) through bash 5.2. An earlier revision of this branch also carried a source-level guard asserting the library never composes PATH throughcompgen, which would have covered that blind spot on any runtime; it was removed during review because this repository's test-quality rule forbids tests that read implementation source, and the rule was judged to win. The removal is deliberate and the trade is recorded here rather than left implicit.Vision alignment (VISION.md at current upstream main): "Logic that can be exact lives in deterministic scripts ... intelligence must never be spent on what a script can do exactly and repeatably" - remote tool resolution is exactly such logic, and it was not deterministic. Also "This repository ships through its own discipline: firstmate work is validated like any other project's, and field incidents become regression coverage." This is a strengthened determinism guarantee inside an existing owner, not a default-behavior change and not a new layer: no new files, no new dependency, one function signature narrowed, one mechanism removed.
Scope notes: no documentation file needed changing beyond the library header's own PATH paragraph, which now states the ordering guarantee. docs/remote-secondmates.md already names bin/fm-remote-job-lib.sh as the single owner of the worker PATH and says nothing that the fix contradicts. Two test failures observed locally (tests/fm-on.test.sh's doctor missing-tools case and tests/fm-remote-doctor.test.sh's stale-worker-identity case, plus a timing-sensitive queue case in tests/fm-remote-job.test.sh) were confirmed pre-existing on pristine HEAD in this environment and are unrelated to this change. The PR should close #2843 only.
What Changed
Risk Assessment
✅ Low: The change is narrowly scoped, aligns PATH composition with the established shell-expansion contract, preserves existing filtering behavior, and removes the prohibited source-reading assertion.
Testing
The focused remote-job test passed under the host Bash 5.3 and an isolated Bash 5.2.37 built after a C17-compatible compiler retry. Before-and-after evidence reproduced the original version-resolution defect and proved the fix, while a queued-worker transcript confirmed sorted resolution and safe handling of glob metacharacters. No source changes or transient worktree artifacts were created.
Evidence: Bash 5.2 before-and-after PATH resolution
Source: Bash 5.2 before-and-after PATH resolution
Evidence: Queued remote worker tool resolution
Source: Queued remote worker tool resolution
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
tests/fm-remote-job.test.sh:187- The new grep reads implementation source and asserts thatcompgenis absent, which the supplied test-quality rule explicitly forbids. It can also fail if unrelatedcompgenusage is added anywhere in this library, while lines 178-182 already verify observable PATH ordering. Remove this assertion or replace it with behavioral execution under Bash 3.2 or 5.2. This conflicts with the accepted intent requiring that the test “pins ... the library must not compose PATH through compgen at all (a source-level guard),” so captain input is required.🔧 Fix: Remove source-reading PATH regression guard
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-remote-job.test.sh/tmp/fm2843-bash52/install/bin/bash tests/fm-remote-job.test.shBash 5.2 before-and-after execution offm_remote_job_compose_operator_pathusing the base and target libraries, followed by executable resolution through the resulting PATHRealfm-remote-job-worker.shqueue flow usingfm_remote_job_stage,fm_remote_job_wait, and a fake multi-version node installation underaccount[qa]Verified the worktree remained clean and no test worker processes remained✅ **Document** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.