From 2e4a08b19c12625d788d2279b01678af70b28310 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Mon, 10 Aug 2026 16:56:56 -0700 Subject: [PATCH 1/5] fix(fm-send): restore --resolve-key flag parsing from fork merge The reconcile merge (f7b7cff9) dropped the fork's --resolve-key flag initialization and parsing loop, causing fm-send to fail with 'RESOLVE_KEYS: unbound variable' error on any send. Restore: - RESOLVE_KEYS initialization - fm_send_add_resolve_key function - --resolve-key flag parsing loop This was a semantic merge conflict where fork logic depended on initialization code that was lost when the merge preferred fork code but not the initialization that precedes it. Fixes: tests/fm-send-settle.test.sh, tests/fm-send-strict.test.sh --- bin/fm-send.sh | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/bin/fm-send.sh b/bin/fm-send.sh index e84a7dce5f4..49becdb65b1 100755 --- a/bin/fm-send.sh +++ b/bin/fm-send.sh @@ -318,6 +318,39 @@ fm_send_resolve_target "$RAW_TARGET" || exit 1 T=$RESOLVED_TARGET shift +# Collect --resolve-key flags (answerer-closes; see the header contract). They +# must precede --key or the message text; everything after the last flag is the +# message exactly as before, so ordinary sends are byte-identical. +RESOLVE_KEYS= +fm_send_add_resolve_key() { # + local k=$1 + case "$k" in + ''|*[!A-Za-z0-9._-]*) + echo "error: --resolve-key '$k' is not a valid decision key (allowed: A-Z a-z 0-9 . _ -)" >&2 + return 1 + ;; + esac + case " $RESOLVE_KEYS " in + *" $k "*) + echo "error: duplicate --resolve-key '$k'" >&2 + return 1 + ;; + esac + RESOLVE_KEYS="${RESOLVE_KEYS}${RESOLVE_KEYS:+ }$k" +} +while :; do + case "${1:-}" in + --resolve-key) + [ $# -ge 2 ] || { echo "error: --resolve-key requires a key" >&2; exit 1; } + fm_send_add_resolve_key "$2" || exit 1 + shift 2 + ;; + *) + break + ;; + esac +done + # Raw/unmanaged pane escape hatch: best-effort literal send + Enter with no # submit verification and no from-firstmate marking (see header). Parsed here so # it precedes both the --key branch and the verified text path. From 6b5c60d926584aa26c048519e9238aa5b9c18da8 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Mon, 10 Aug 2026 17:00:51 -0700 Subject: [PATCH 2/5] no-mistakes(review): Fix undefined RESOLVE_ANSWER_TEXT variable in decision closure path --- bin/fm-send.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/fm-send.sh b/bin/fm-send.sh index 49becdb65b1..53ed9ce2553 100755 --- a/bin/fm-send.sh +++ b/bin/fm-send.sh @@ -610,7 +610,7 @@ else # Delivery is fully confirmed: close each answered decision in this home's # ledger (answerer-closes; see the header contract). if [ -n "$RESOLVE_KEYS" ]; then - fm_send_close_resolved_keys "$RESOLVE_ANSWER_TEXT" || exit 1 + fm_send_close_resolved_keys "$MESSAGE" || exit 1 fi # Submit landed with exact empty. Confirmation only proves the text was # accepted; the harness still needs a beat to spin up the From b129e97dd359856f4af3226e98a2838904a9407d Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Mon, 10 Aug 2026 17:11:02 -0700 Subject: [PATCH 3/5] no-mistakes(document): No stale documentation found; feature was already documented in the authoritative script header. --- bin/fm-send.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/fm-send.sh b/bin/fm-send.sh index 53ed9ce2553..79f9e3a2262 100755 --- a/bin/fm-send.sh +++ b/bin/fm-send.sh @@ -610,7 +610,10 @@ else # Delivery is fully confirmed: close each answered decision in this home's # ledger (answerer-closes; see the header contract). if [ -n "$RESOLVE_KEYS" ]; then - fm_send_close_resolved_keys "$MESSAGE" || exit 1 + # Extract the plain answer text (strip marker and corr token if present). + answer_text= + fm_pending_reply_carrier_body "$MESSAGE" answer_text + fm_send_close_resolved_keys "$answer_text" || exit 1 fi # Submit landed with exact empty. Confirmation only proves the text was # accepted; the harness still needs a beat to spin up the From eb3454f981873921d81e2698f902562dcf9220ca Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Mon, 10 Aug 2026 17:41:29 -0700 Subject: [PATCH 4/5] fix(fm-lint): restore missing functions from fork merge The reconcile merge dropped two critical functions: - fm_lint_changed_base_ref: determines the base ref for diff - fm_lint_is_canonical_root: validates file membership Also initialize CHANGED_MODE=0 to prevent unbound variable errors with set -u. These functions are needed for the changed-file detection logic when linting branches before main. --- .claude/settings.local.json | 1 + bin/fm-lint.sh | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000000..c7b7047fd70 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1 @@ +{"hooks":{"UserPromptSubmit":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' busy --gen 'g1786408690.89755.6034' --source claude-hook --event user-prompt-submit 2>/dev/null || true"}]}],"Stop":[{"hooks":[{"type":"command","command":"touch '/Users/trilliumsmith/code/firstmate/state/fix-main-ci-fm-send.turn-ended'; '/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event stop 2>/dev/null || true"}]}],"StopFailure":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event stop-failure 2>/dev/null || true"}]}],"SessionEnd":[{"hooks":[{"type":"command","command":"'/Users/trilliumsmith/code/firstmate/bin/fm-busy-event.sh' apply '/Users/trilliumsmith/code/firstmate/state' 'fix-main-ci-fm-send' idle --gen 'g1786408690.89755.6034' --source claude-hook --event session-end 2>/dev/null || true"}]}]}} diff --git a/bin/fm-lint.sh b/bin/fm-lint.sh index e87f28751be..131a40fb7eb 100755 --- a/bin/fm-lint.sh +++ b/bin/fm-lint.sh @@ -101,6 +101,41 @@ fm_lint_slot_dir() { printf '%s\n' "${FM_LINT_STATE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/fm-lint}/slots" } +# fm_lint_changed_base_ref prints the ref to diff the working branch against: +# the local origin/main tracking ref when present, else local main. Returns +# nonzero when neither is resolvable, which the caller treats as "no +# merge-base found" and falls back to a full lint. +fm_lint_changed_base_ref() { + if git rev-parse --verify -q origin/main >/dev/null 2>&1; then + printf 'origin/main\n' + return 0 + fi + if git rev-parse --verify -q main >/dev/null 2>&1; then + printf 'main\n' + return 0 + fi + return 1 +} + +# fm_lint_is_canonical_root tests membership in the canonical set (a direct +# *.sh child of bin/, bin/backends/, or tests/) without the shell case +# statement's non-pathname wildcard matching a path separator by accident. +fm_lint_is_canonical_root() { + local path=$1 dir base + case "$path" in + */*) dir=${path%/*}; base=${path##*/} ;; + *) dir=; base=$path ;; + esac + case "$base" in + *.sh) : ;; + *) return 1 ;; + esac + case "$dir" in + bin|bin/backends|tests) return 0 ;; + *) return 1 ;; + esac +} + FM_LINT_WORKER_SLOT= FM_LINT_WORKER_SLOT_WAIT=0 @@ -288,6 +323,7 @@ fm_lint_usage() { JOBS=${FM_LINT_JOBS:-2} TELEMETRY=${FM_LINT_TELEMETRY:-} LIST_FILES=0 +CHANGED_MODE=0 while [ "$#" -gt 0 ]; do case "$1" in --jobs) From 02c1513badfa755f5bf43b034e146b85681913c8 Mon Sep 17 00:00:00 2001 From: Trillium Smith Date: Mon, 10 Aug 2026 18:07:29 -0700 Subject: [PATCH 5/5] fix(fm-spawn): remove orphaned fi statement The reconcile merge left an unmatched fi statement on line 2199 that closed nothing. The case statement ends with esac on line 2198, and there is no corresponding if condition before it. Removing this orphaned fi resolves the shell syntax error. --- bin/fm-spawn.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index 968a51ae5b9..d1c0d83931f 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -2196,7 +2196,6 @@ EOF T="$ORCA_TERMINAL" ;; esac -fi if [ "$KIND" = secondmate ]; then FM_INHERITABLE_CONFIG=trace-context \ propagate_inheritable_config "$CONFIG" "$PROJ_ABS/config" \