Skip to content

Sync upstream main (11 commits), and fix the Windows ancestry walk it exposed - #3

Merged
FLEXZICAN merged 14 commits into
mainfrom
sync/upstream-11
Jul 29, 2026
Merged

FLEXZICAN merged 14 commits into
mainfrom
sync/upstream-11

Conversation

@FLEXZICAN

@FLEXZICAN FLEXZICAN commented Jul 29, 2026 •

Copy link
Copy Markdown
Owner

Three commits: a faithful merge, then two fixes the merge exposed.

1. The merge

11 upstream commits. Two conflicts.

bin/fm-session-lock-lib.sh - upstream rewrote fm_harness_ancestry_pid (kunchenguid#1206) to walk 16 hops and, for Claude, to keep walking a contiguous claude-named run to the outermost pid. Windows short-circuits into its own walk before that loop, so upstream's new POSIX body is taken unchanged with the Windows dispatch above it.

tests/fm-backend.test.sh - upstream refined the old-bin fixture comment and list. Kept upstream's comment and the union of the sibling lists; this branch's five extra entries are required by the transitive-source-closure guard upstream does not have.

Worth knowing about what arrived:

2. Windows ancestry walk had the same bug upstream just fixed

Upstream's kunchenguid#1206 describes a chain of hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude(lock). The Windows walk had the same shape and the same bug, and the nesting is confirmed present on Windows.

Left alone, the two platforms would disagree about which pid owns a lock, and tokenless watcher re-arm would be silently dead on Windows for Claude Code - the primary harness here.

Both Windows stages now share one match rule with the POSIX walk: first match wins, except a claude-named match extends through a contiguous claude-named run to the outermost pid, bounded by the first non-match so an unrelated claude further up the process tree is never absorbed. Hop limit 8 to 16 to match.

The rule is pinned as a pure function over synthetic chains. Upstream's end-to-end test stages real nested processes, so it only exercises the POSIX walk; Windows resolves ancestry from process-table snapshots a test cannot stage as live processes.

3. A repo invariant was passing without checking anything on Windows

CI caught a violation in the fixture chain added by commit 2. Reproducing it locally revealed why it had to be CI that caught it: git grep -F "/Users/" finds nothing on Windows, because Git Bash rewrites an argument that looks like an absolute POSIX path into a Windows one before the native git.exe sees it. The pattern arrived as a path under the Git installation directory and matched nothing.

Measured on the same tree: 0 matches with the literal pattern, 1 with either MSYS2_ARG_CONV_EXCL='*' or a regex whose first character is a bracket expression. The bracket form is used: no environment variable, identical behaviour everywhere, and it keeps the property the old string-splitting trick provided - the pattern cannot match its own source.

Same class as the python3 Store-alias bug, and the second one found on Windows: a check that resolves, reports success, and validates nothing.

Verification

CI 10/10 green. Windows lane 26/26 and lint clean locally. fm-claude-stop-autoarm passes on Windows including upstream's new bg-spare case - the test that would have caught commit 2's bug had the walk been shared from the start.

kunchenguid and others added 14 commits July 28, 2026 10:39
…nguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes
* fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2

fm-brief.sh built each Definition-of-done block and the not-enabled
Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS
/bin/bash) the lexer scans for the command substitution's closing `)`
textually and tracks quote state through the heredoc body, so a single
apostrophe, unbalanced quote, or unbalanced paren in that prose breaks
parsing of the whole script. Every ship-brief scaffold (no-mistakes,
direct-PR, local-only) failed with `unexpected EOF while looking for
matching )`. Bash 4+ parses it fine, so the breakage stayed invisible
everywhere except stock macOS.

Replace all four command-substitution heredocs with
`IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)`
wrapper and the entire defect class regardless of future prose, and
preserves the variable expansion the direct-PR and local-only bodies
need. `read` keeps the heredoc's trailing newline that `$(...)` used to
strip, so trim one newline to keep every generated brief byte-identical
to prior output.

Guard the structure, not one historical phrase: a new test rejects any
heredoc nested in a command substitution anywhere in fm-brief.sh, where
the old assertion pinned a single apostrophe phrase and so missed the
reintroduction. Extend the stock-macOS Bash CI job from parsing one
script to the whole maintained shell surface (bin/*.sh,
bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file
set so parse scope and lint scope cannot drift apart.

* no-mistakes(review): Captain: harden Bash structure and inventory guards

* no-mistakes(document): Align stock macOS Bash contributor checks

* no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings
* fix(test): pin teardown tmux baseline to historical kill selectors

merge-base HEAD main collapses to HEAD after the exact-selector change
lands on the default branch, so the old teardown fixture was accidentally
exercising current exact targets. Resolve a content-historical permissive
tmux adapter from first-parent history and force that post-squash topology
inside the conformance case so main and feature branches keep the same
old-vs-new contract.

* no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings
…id#1197)

Cut the runtime skill to the compact pace-aware selection procedure plus
minimum owner pointers. Keep every distinct decision rule and move expanded
acceptance scenarios to deterministic fixture ownership assertions.

Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction).
…1219)

* Inherit config/backend into secondmate homes with deliberate-override preservation

Add backend to the shared inheritable config allowlist so launch, locked
bootstrap, and config-push converge a primary pin into secondmate homes as each
home local future-spawn default. Track last-inherited bytes in a private state
provenance marker so deliberate per-home overrides survive present and absent
primary convergence, keep --backend and FM_BACKEND stronger, and extend the
existing inheritance tests plus docs and skill claims.

* no-mistakes(review): Preserve equal unprovenanced backend overrides

* no-mistakes(review): Preserve symlink overrides and verify spawn precedence

* no-mistakes(review): Snapshot backend inheritance for consistent provenance

* no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence

* no-mistakes(document): Document inherited backend override preservation

* fix: restore primary-authoritative backend inheritance after document regression

The document step reintroduced provenance and deliberate per-home override
semantics after review had simplified config/backend to plain primary-authoritative
allowlist membership. Restore the primary-always-wins path: present overwrites,
absent removes, no provenance marker, and docs/tests match that contract.

* no-mistakes(review): Add divergent backend precedence regression fixtures

* no-mistakes(document): Document backend inheritance contract
* fix(pi): remove Calm's exclusive Pi upper-version ceiling

tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS
allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs
described that range as "supported" rather than verified evidence. The
Calm CHANGELOG shows no API introduced at either version, so there is no
evidence for a real minimum; the presentation adapters already probe the
exact method they patch rather than checking a version.

Replace the allowlist with dated version evidence that never rejects a
newer Pi, and make each presentation adapter degrade independently with
a diagnostic if a future Pi removes its API, instead of the whole Calm
extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1
through 0.82.0" phrasing to state it as verified evidence, not a
ceiling.

* no-mistakes(review): Probe missing Calm adapter exports safely

* no-mistakes(document): Document Calm's unbounded Pi compatibility
…enguid#1204)

* fix(guard): allow session-local todo tools in the primary

The delegation-shape guard denied TaskCreate and TaskUpdate because their
normalized names contain the `task` stem. Those tools write only the harness's
session-local todo list, which has no executor: it spawns no agent, allocates
no worktree, registers no schedule, and starts nothing that outlives the
session. That is not the unaccounted work the guard exists to stop, so the stem
match was a false positive, and the deny text told the primary to run
bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry.

Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than
widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only
observe or stop existing work. Both lists stay exact-name so neither can widen
by substring.

Tests cover the two allowed names and six near-miss names that a substring or
shortened-stem widening would release; both mutations were watched red.

* no-mistakes(review): drop session-local todo tools from recommended deny list

* no-mistakes: apply CI fixes
…claude pid (kunchenguid#1206)

* fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid

fm_harness_ancestry_pid() previously returned the first ancestor process
whose command matched a verified harness name. Claude Code's Stop hook
fires as a bg-spare worker several levels below the session's actual
lock-owning claude process (hook shell -> claude bg-spare ->
claude bg-pty-host -> claude -> claude(lock)), so the first match was
the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self()
then never matched state/.lock, and the Claude Stop auto-arm silently
treated its own primary session as an unrelated live owner and never
armed the watcher.

The walk now keeps going past a claude-named match, looking for a still
more ancestral claude-named match, and stops the instant a non-match
follows an already-found match (bounding it to a contiguous run rather
than the literal ancestry top, so an unrelated claude-named process
further up the real process tree is never mistaken for part of this
session's own nested chain). Every other harness keeps the original
first-match-wins behavior, since e.g. Pi's shared signed-wrapper
ancestry actually holds the session at the inner engine pid, not an
outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper
bg-spare chain.

* no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim

* no-mistakes: apply CI fixes
* fix: confirm watcher startup on MSYS

* no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test

* no-mistakes(review): validate OpenCode ready timeout, make uname cache internal
…d#1195)

* fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates

Crewmate panes are created by a long-lived tmux/herdr daemon that does not
inherit firstmate's current environment. When firstmate runs under a non-default
CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare
`claude` in the crewmate pane fell back to the default ~/.claude store and
launched unauthenticated, blocking the crewmate before it could do any work.

fm-spawn now prefixes the claude launch with firstmate's own resolved
CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config
store firstmate is authenticated with. An unset value is the single-store
default and adds no prefix; non-claude harnesses are unaffected.

Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set,
omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test
helper so launch assertions no longer depend on the developer's environment.

* no-mistakes: apply CI fixes
…guid#1233)

* fix: preserve dispatch harness identity

* no-mistakes(review): Fix Grok counterfactual tuple validation

* no-mistakes(document): Scope dispatch authentication to selected tuple

* fix: restore dispatch instruction budget

* no-mistakes(review): Scope dispatch authentication after candidate selection
Two conflicts, both resolved by keeping the Windows branch and taking
upstream's work wholesale on the POSIX side.

bin/fm-session-lock-lib.sh: upstream rewrote fm_harness_ancestry_pid to walk
16 hops and, for Claude, to keep walking a contiguous claude-named run to the
OUTERMOST pid (kunchenguid#1206), because the Stop hook fires several levels below the
lock owner. Windows short-circuits into its own walk before that loop, so the
new POSIX body is taken unchanged and the Windows dispatch sits above it.

tests/fm-backend.test.sh: upstream refined the old-bin fixture comment and
list; kept upstream's comment and the union of the sibling lists, since this
branch's five extra entries are required by the transitive-source-closure
guard upstream does not have.

Notable in what arrived: upstream added its own Windows/MSYS support for
watcher-arm confirmation (kunchenguid#1212), in a different area from this branch's work
(spawn, session lock, symlinks, line endings). It carries a second uname cache
in bin/fm-wake-lib.sh alongside this branch's platform seam; left as-is rather
than refactored, so a file upstream is actively changing does not become a
permanent conflict point.

Upstream's kunchenguid#1195 adds a CLAUDE_CONFIG_DIR env prefix to the launch line, which
is one more reason the Windows pane must be a POSIX shell before the launch is
sent. The auto-merge placed it correctly, ahead of the pane setup.
Upstream's kunchenguid#1206 fixed the POSIX ancestry walk: Claude Code's Stop hook fires
as a bg-spare worker several levels below the session that actually holds the
lock (hook shell -> claude bg-spare -> claude bg-pty-host -> claude -> claude),
so a first-match-wins walk resolved to the worker. The auto-arm then treated
its own session as an unrelated live owner and never armed the watcher.

The Windows walk had the same shape and the same bug. Left alone, the two
platforms would disagree about which pid owns a lock, and tokenless watcher
re-arm would be silently dead on Windows for the project's primary harness.

Both Windows stages now share one match rule with the POSIX walk: first match
wins, except that a claude-named match extends through a contiguous run of
claude-named ancestors to the outermost pid, bounded by the first non-match so
an unrelated claude further up the real process tree is never absorbed. Hop
limit raised from 8 to 16 to match, since the bg-spare chain is deeper than the
original bound.

The deferred node/python interpreter check moves to a second pass over the same
chain, so it still costs nothing unless no direct name match exists anywhere.

The rule is pinned as a pure function over synthetic chains, because the
end-to-end test upstream added stages real nested processes and therefore only
exercises the POSIX walk; Windows resolves ancestry from process-table
snapshots a test cannot stage as live processes.

Verified on Windows: fm-claude-stop-autoarm passes, including upstream's new
bg-spare case. Windows lane 26/26, lint clean.
`git grep -F "/Users/"` finds nothing on Windows. Git Bash rewrites an argument
that looks like an absolute POSIX path into a Windows one before the native
git.exe sees it, so the pattern arrived as a path under the Git installation
directory and matched nothing. The invariant therefore passed without checking
anything on Windows, which is the worst shape of failure: a green check that
inspected nothing.

Measured on the same tree: 0 matches with the literal pattern, 1 with either
MSYS2_ARG_CONV_EXCL='*' or a regex whose first character is a bracket
expression. The bracket form is used because it needs no environment variable
and behaves identically on every platform, and it also keeps the property the
old string-splitting trick provided: the pattern cannot match its own source.

Also fixes the violation the working check immediately caught, in the fixture
chain added by the previous commit.

Same class as the python3 Store-alias bug, and the second one found on Windows:
a check that resolves, reports success, and validates nothing.
@FLEXZICAN
FLEXZICAN merged commit 9cfbda7 into main Jul 29, 2026
10 of 12 checks passed
@FLEXZICAN
FLEXZICAN deleted the sync/upstream-11 branch July 31, 2026 10:27
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.

7 participants