Skip to content

fix(bin): refuse Pi worker launches whose --model lacks an exact credentialed match - #4

Merged
gkalyan merged 4 commits into
mainfrom
fm/fm-pi-model-provider-guard
Sep 25, 2026
Merged

gkalyan merged 4 commits into
mainfrom
fm/fm-pi-model-provider-guard

Conversation

@gkalyan

@gkalyan gkalyan commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Intent

Guard it in the spawn tool: refuse to launch a Pi worker unless the model resolves to Anthropic, and say why.

Context for that ask: on 2026-09-25 three Pi workers were launched with --model sonnet. Pi resolved that bare alias to Amazon Bedrock's us.anthropic.claude-sonnet-5, which has no credentials on this machine, so each worker printed Error: No API key found for amazon-bedrock and sat idle at its prompt doing nothing, while its supervisor reported the work under way for about 40 minutes. Earlier the same day a deploy worker launched with --model 'claude-opus-5[1m]' (Claude Code syntax) exited at once with Error: Model "claude-opus-5[1m]" not found. The exact ids claude-sonnet-5 and claude-opus-5 resolve to the Anthropic subscription provider and work. Pi's defaultProvider: anthropic setting was already set and did not prevent the Bedrock resolution. The spawn tool launched all of these without objection.

What Changed

  • bin/fm-spawn.sh adds pi_model_validate, which now runs before every pi or pi-signed launch. It reads <pi-bin> --list-models, which lists only providers that have usable credentials. The model is accepted only if it matches exactly one listed row, either by bare id or by exact provider/id. The launch is refused, with a stated reason, when:

    • the model is a bare or fuzzy alias such as sonnet
    • it uses a Claude Code suffix such as claude-opus-5[1m]
    • it is an unlisted id
    • it matches more than one row
    • the listing can't be read or is empty

    Each refusal lists the unambiguous provider/id selectors that would work. An empty or default model is not checked. codex-native/<id> is also exempt because fm-harness.sh validates it separately.

  • The Pi harness reference (harness-adapters/references/harness/pi.md) now explains the difference between Pi's two model-selection surfaces. --list-models shows only credentialed providers, while --model fuzzy-matches with no such filter. That gap is how sonnet resolved to Bedrock's us.anthropic.claude-sonnet-5 on 2026-09-25.

  • Adds tests/fm-pi-model-guard.test.sh and registers it in bin/fm-test-run.sh. The fake Pi in tests/fm-spawn-dispatch-profile.test.sh now answers --list-models with a small catalog, so existing spawn tests still pass the new guard.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The fix-round change is a small awk filter. It keeps a suggested selector only when that selector matches exactly one row under the guard's own match rule (bare id or provider/id). A regression test covers a colliding selector: it fails on the old code, and it also confirms that the unambiguous selector the refusal suggests is accepted on retry.

Testing

First I reproduced both 2026-09-25 failures with the real Pi 0.87.1. Then I drove the real fm-spawn.sh with the real Pi catalog (only the tmux pane launch is stubbed), once on the target commit and once on the base commit. On the target commit, the incident models (bare sonnet, [1m] suffix, unlisted Bedrock id) exit 1 with an explanation and a list of exact ids, and no worker or metadata is created. Exact ids and the gated codex-native pathway still launch. On the base commit, every one of these models launched. I also ran a suggested selector through real Pi, and it answered 'ok'. All live scenarios passed. The two targeted suites, fm-pi-model-guard and fm-spawn-dispatch-profile, pass. They cover the ambiguous, colliding-selector, unreadable-catalog and non-Anthropic cases, which this machine's single-provider live catalog cannot produce, so those cases remain untested live.

  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Operator spawns a Pi worker with bare alias --model sonnet: spawn refuses (exit 1), explains that Pi would fuzzy-match to a possibly uncredentialed provider, lists exact ids, and launches no worker ✅ pass live 02-fm-spawn-live-after.txt (live-sonnet); base comparison in 03-fm-spawn-live-before.txt shows it launched before
Operator spawns a pi-signed worker with Claude Code syntax claude-opus-5[1m]: refused with reason, no worker launched ✅ pass live 02-fm-spawn-live-after.txt (live-opus1m)
Adversarial: operator names the uncredentialed Bedrock model explicitly (amazon-bedrock/us.anthropic.claude-sonnet-5): refused because it is absent from the real credentialed catalog ✅ pass live 02-fm-spawn-live-after.txt (live-bedrock)
Operator spawns with the working exact ids claude-opus-5 (pi) and claude-sonnet-5 (pi-signed): launch proceeds with the model on the launch line and in meta ✅ pass live 02-fm-spawn-live-after.txt (live-opus, live-sonnetexact)
Operator retries with a suggested provider/id selector: spawn accepts it and real Pi runs it ✅ pass live 02-fm-spawn-live-after.txt (live-provid); 04-real-pi-accepted-ids-run.txt
The separately gated codex-native/&lt;id&gt; --effort ultra pathway still launches ✅ pass live 02-fm-spawn-live-after.txt (live-native)
Ambiguous bare id, colliding selector, unreadable catalog and listed non-Anthropic provider are handled (refuse, suggest only unambiguous selectors, refuse, accept) ⏸️ untested no The prior payload did not establish a live result for this scenario. This machine's real Pi catalog lists only the anthropic provider, so a live run cannot create multi-provider or unreadable-catalog…
Evidence: Real Pi reproduces the incident (sonnet→Bedrock no key; [1m] not found)

Source: Real Pi reproduces the incident (sonnet→Bedrock no key; [1m] not found)

$ pi --model sonnet -p "reply ok"
No API key found for amazon-bedrock.

Use /login to log into a provider via OAuth or API key. See:
  /opt/homebrew/lib/node_modules/@earendil-works/pi-coding-agent/docs/providers.md
  /opt/homebrew/lib/node_modules/@earendil-works/pi-coding-agent/docs/models.md
exit=

$ pi --model 'claude-opus-5[1m]' -p 'reply ok'
Error: Model "claude-opus-5[1m]" not found. Use --list-models to see available models.
exit=
Evidence: fm-spawn with guard, real Pi catalog: refusals + accepted launches

Source: fm-spawn with guard, real Pi catalog: refusals + accepted launches

##### TARGET commit 930e658 (with guard)
=== $ fm-spawn live-sonnet <proj> --harness pi --model 'sonnet'  --scout
error: Pi model 'sonnet' matches no entry in '/tmp/fm-live.mkWZ/sonnet/fake/fakebin/pi --list-models' (Pi would fuzzy-match it, possibly to an uncredentialed provider); use one of these exact ids: anthropic/claude-fable-5 anthropic/claude-fable-5-1 anthropic/claude-haiku-4-5 anthropic/claude-haiku-4-5-20251001 anthropic/claude-opus-4-5 anthropic/claude-opus-4-5-20251101 anthropic/claude-opus-4-6 anthropic/claude-opus-4-7 anthropic/claude-opus-4-8 anthropic/claude-opus-5 anthropic/claude-opus-5-5 anthropic/claude-sonnet-4-5 anthropic/claude-sonnet-4-5-20250929 anthropic/claude-sonnet-4-6 anthropic/claude-sonnet-5
exit=1
worker NOT launched
meta: none

=== $ fm-spawn live-opus1m <proj> --harness pi-signed --model 'claude-opus-5[1m]'  --scout
error: Pi model 'claude-opus-5[1m]' matches no entry in '/tmp/fm-live.mkWZ/opus1m/fake/fakebin/pi-signed --list-models' (Pi would fuzzy-match it, possibly to an uncredentialed provider); use one of these exact ids: anthropic/claude-fable-5 anthropic/claude-fable-5-1 anthropic/claude-haiku-4-5 anthropic/claude-haiku-4-5-20251001 anthropic/claude-opus-4-5 anthropic/claude-opus-4-5-20251101 anthropic/claude-opus-4-6 anthropic/claude-opus-4-7 anthropic/claude-opus-4-8 anthropic/claude-opus-5 anthropic/claude-opus-5-5 anthropic/claude-sonnet-4-5 anthropic/claude-sonnet-4-5-20250929 anthropic/claude-sonnet-4-6 anthropic/claude-sonnet-5
exit=1
worker NOT launched
meta: none

=== $ fm-spawn live-bedrock <proj> --harness pi --model 'amazon-bedrock/us.anthropic.claude-sonnet-5'  --scout
error: Pi model 'amazon-bedrock/us.anthropic.claude-sonnet-5' matches no entry in '/tmp/fm-live.mkWZ/bedrock/fake/fakebin/pi --list-models' (Pi would fuzzy-match it, possibly to an uncredentialed provider); use one of these exact ids: anthropic/claude-fable-5 anthropic/claude-fable-5-1 anthropic/claude-haiku-4-5 anthropic/claude-haiku-4-5-20251001 anthropic/claude-opus-4-5 anthropic/claude-opus-4-5-20251101 anthropic/claude-opus-4-6 anthropic/claude-opus-4-7 anthropic/claude-opus-4-8 anthropic/claude-opus-5 anthropic/claude-opus-5-5 anthropic/claude-sonnet-4-5 anthropic/claude-sonnet-4-5-20250929 anthropic/claude-sonnet-4-6 anthropic/claude-sonnet-5
exit=1
worker NOT launched
meta: none

=== $ fm-spawn live-opus <proj> --harness pi --model 'claude-opus-5'  --scout
exit=0
worker LAUNCHED: --model 'claude-opus-5'
meta: model=claude-opus-5

=== $ fm-spawn live-sonnetexact <proj> --harness pi-signed --model 'claude-sonnet-5'  --scout
exit=0
worker LAUNCHED: --model 'claude-sonnet-5'
meta: model=claude-sonnet-5

=== $ fm-spawn live-provid <proj> --harness pi --model 'anthropic/claude-opus-5-5'  --scout
exit=0
worker LAUNCHED: --model 'anthropic/claude-opus-5-5'
meta: model=anthropic/claude-opus-5-5

=== $ fm-spawn live-native <proj> --harness pi --model 'codex-native/gpt-6-astra' --effort ultra --scout
exit=0
worker LAUNCHED: --model 'codex-native/gpt-6-astra'
meta: model=codex-native/gpt-6-astra
Evidence: fm-spawn at base commit: launches sonnet, [1m], Bedrock without objection

Source: fm-spawn at base commit: launches sonnet, [1m], Bedrock without objection

##### BASE commit 1e8058e (before guard)
=== $ fm-spawn live-sonnet <proj> --harness pi --model 'sonnet'  --scout
exit=0
worker LAUNCHED: --model 'sonnet'
meta: model=sonnet

=== $ fm-spawn live-opus1m <proj> --harness pi-signed --model 'claude-opus-5[1m]'  --scout
exit=0
worker LAUNCHED: --model 'claude-opus-5[1m]'
meta: model=claude-opus-5[1m]

=== $ fm-spawn live-bedrock <proj> --harness pi --model 'amazon-bedrock/us.anthropic.claude-sonnet-5'  --scout
exit=0
worker LAUNCHED: --model 'amazon-bedrock/us.anthropic.claude-sonnet-5'
meta: model=amazon-bedrock/us.anthropic.claude-sonnet-5

=== $ fm-spawn live-opus <proj> --harness pi --model 'claude-opus-5'  --scout
exit=0
worker LAUNCHED: --model 'claude-opus-5'
meta: model=claude-opus-5

=== $ fm-spawn live-sonnetexact <proj> --harness pi-signed --model 'claude-sonnet-5'  --scout
exit=0
worker LAUNCHED: --model 'claude-sonnet-5'
meta: model=claude-sonnet-5

=== $ fm-spawn live-provid <proj> --harness pi --model 'anthropic/claude-opus-5-5'  --scout
exit=0
worker LAUNCHED: --model 'anthropic/claude-opus-5-5'
meta: model=anthropic/claude-opus-5-5

=== $ fm-spawn live-native <proj> --harness pi --model 'codex-native/gpt-6-astra' --effort ultra --scout
exit=0
worker LAUNCHED: --model 'codex-native/gpt-6-astra'
meta: model=codex-native/gpt-6-astra
Evidence: Suggested exact ids really run in Pi

Source: Suggested exact ids really run in Pi

$ pi --model claude-sonnet-5 -p 'reply with exactly: ok'
ok

$ pi --model anthropic/claude-sonnet-5 -p 'reply with exactly: ok'
ok
Evidence: Live driver script

Source: Live driver script

#!/usr/bin/env bash
# Drives real bin/fm-spawn.sh with the REAL installed Pi 0.87.1 answering
# --help/--list-models (real HOME => real credentials/catalog). Only tmux and
# treehouse are stubbed so no actual worker pane is created.
set -u
REPO=$1; shift
REAL_HOME=$HOME
ROOT=$REPO
. "$REPO/tests/fixtures.sh"
TMP_ROOT=$(mktemp -d /tmp/fm-live.XXXX)
drive() { # <label> <harness> <model> [extra...]
  local label=$1 harness=$2 model=$3; shift 3
  local d=$TMP_ROOT/$label home proj wt fakebin
  home=$d/home; proj=$d/project; wt=$d/wt
  fakebin=$(make_spawn_fakebin "$d/fake")
  for t in pi pi-signed; do
    printf '#!/usr/bin/env bash\nHOME=%q exec /opt/homebrew/bin/pi "$@"\n' "$REAL_HOME" > "$fakebin/$t"; chmod +x "$fakebin/$t"
  done
  fm_test_spawn_home "$home" "$harness"
  fm_git_worktree "$proj" "$wt" "wt-$label" >/dev/null 2>&1
  fm_test_spawn_brief "$home" "live-$label"
  : > "$d/launch.log"
  echo "=== \$ fm-spawn live-$label <proj> --harness $harness --model '$model' $* --scout"
  out=$(FM_FAKE_LAUNCH_LOG="$d/launch.log" fm_test_run_spawn "$home" "$wt" "$fakebin" "live-$label" "$proj" --harness "$harness" --model "$model" "$@" --scout 2>&1)
  st=$?
  printf '%s\n' "$out" | grep -E 'error|Pi model' | head -5
  echo "exit=$st"
  if [ -s "$d/launch.log" ]; then echo "worker LAUNCHED: $(grep -o -- "--model '[^']*'" "$d/launch.log" | head -1)"; else echo "worker NOT launched"; fi
  [ -e "$home/state/live-$label.meta" ] && echo "meta: $(grep '^model=' "$home/state/live-$label.meta")" || echo "meta: none"
  echo
}
drive sonnet pi sonnet
drive opus1m pi-signed 'claude-opus-5[1m]'
drive bedrock pi amazon-bedrock/us.anthropic.claude-sonnet-5
drive opus pi claude-opus-5
drive sonnetexact pi-signed claude-sonnet-5
drive provid pi anthropic/claude-opus-5-5
drive native pi codex-native/gpt-6-astra --effort ultra
rm -rf "$TMP_ROOT"

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (2) ✅
  • ⚠️ bin/fm-spawn.sh:2082 - The new guard refuses every Pi or pi-signed model whose catalog provider is not anthropic, including models that are credentialed and listed. The intent asks for exactly that ("refuse to launch a Pi worker unless the model resolves to Anthropic"), but the rest of the repo still treats non-Anthropic Pi profiles as supported. tests/fm-dispatch-resolve.test.sh:377-379 resolves to --harness &#39;pi&#39; --model &#39;openai-codex/gpt-5.6-sol&#39;. tests/fm-bootstrap.test.sh:1142-1165 accepts crew-dispatch profiles pi:openai-codex/... and pi:kimi-code/k3. docs/verification/dispatch-auth.md:128-168 documents pi:openai-codex, pi:xai and pi:kimi-coding candidates. After this change, fm-dispatch-resolve can still recommend a Pi profile that fm-spawn always refuses. The commit also changed the two dispatch-profile tests from openai-codex/gpt-5.6-sol to claude-opus-5 instead of keeping that path covered. Please confirm that dropping Pi-hosted OpenAI-Codex, Kimi and xAI workers is intended. If it is, the dispatch resolver and config validation should stop offering those profiles. If it is not, the guard should refuse only unlisted, aliased or ambiguous models.
  • ⚠️ bin/fm-spawn.sh:1606 - The refusal message suggests bare Anthropic ids (anthropic_ids is built from column 2 only), but the guard itself refuses a bare id that matches more than one row. Example: a machine where another credentialed provider (such as github-copilot) also lists claude-sonnet-5. --model sonnet is refused with 'use one of these Anthropic ids: ... claude-sonnet-5'. Retrying with --model claude-sonnet-5 is then refused as ambiguous with the same suggestion, so the operator never gets a working id. Fix: build the suggestions as anthropic/&lt;id&gt;, which the guard's own exact provider/id match accepts, at least when the bare id is ambiguous.
  • ℹ️ bin/fm-spawn.sh:1611 - The guard accepts a bare id such as claude-sonnet-5 when it matches a single listed anthropic row. The new pi.md text and the guard's own comment say the listing hides uncredentialed providers and can never prove an aliased or unlisted id is safe. A bare id Pi resolves over its full catalog could hit the same blind spot if an uncredentialed provider has the same bare id. The intent reports that these exact ids work today, so this is not a current failure. A narrower option would be to pass anthropic/&lt;id&gt; to Pi after the check, which pins the provider; that would change launch arguments, so it needs the author's decision.
  • ℹ️ tests/fm-pi-model-guard.test.sh:17 - The header comment says 'tests/fm-pi-model-guard-live-e2e.test.sh is the default-on live guard that proves the same refusal against the REAL installed Pi catalog', but the change does not add that file and it does not exist in the tree. The only live check is the manual one mentioned in the commit message. Remove the sentence or add the test.

🔧 Fix applied.
2 issues (1 warning, 1 info) still open:

  • ⚠️ bin/fm-spawn.sh:1616 - The refusal message now lists every catalog row as provider/id, but the guard's own match treats $2 == m and ($1 &#34;/&#34; $2) == m the same way. So a suggested selector can match a second row whose bare id contains a slash, and the guard then refuses it as ambiguous. Example: an OpenRouter key is configured and Pi lists openrouter anthropic/claude-sonnet-5 next to anthropic claude-sonnet-5. --model sonnet is refused with a suggestion that includes anthropic/claude-sonnet-5. Retrying with that id matches both rows (the provider/id of the anthropic row and the bare id of the openrouter row), so it is refused as ambiguous. This is the same dead-end the previous round's ambiguous-suggestion-recommends-refused-id fix was meant to close. The new test only covers ids that contain no slash (github-copilot). Fix: build selectors only from entries that match exactly one row under the same rule, e.g. drop any provider/id that also equals some row's bare id.
  • ℹ️ bin/fm-spawn.sh:1603 - The stated intent says "refuse to launch a Pi worker unless the model resolves to Anthropic". The guard now accepts any provider with exactly one listed match (for example openai-codex/gpt-5.6-sol). This departure is on purpose: the user's fix-round instructions for pi-guard-blocks-documented-non-anthropic-pi-profiles removed the Anthropic-only check and treat an exact match against --list-models as the credential check. It still closes the incident paths (sonnet, claude-opus-5[1m], and unlisted Bedrock ids are all refused), so no action is needed.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Operator spawns a Pi worker with bare alias --model sonnet: spawn refuses (exit 1), explains that Pi would fuzzy-match to a possibly uncredentialed provider, lists exact ids, and launches no worker ✅ pass live 02-fm-spawn-live-after.txt (live-sonnet); base comparison in 03-fm-spawn-live-before.txt shows it launched before
Operator spawns a pi-signed worker with Claude Code syntax claude-opus-5[1m]: refused with reason, no worker launched ✅ pass live 02-fm-spawn-live-after.txt (live-opus1m)
Adversarial: operator names the uncredentialed Bedrock model explicitly (amazon-bedrock/us.anthropic.claude-sonnet-5): refused because it is absent from the real credentialed catalog ✅ pass live 02-fm-spawn-live-after.txt (live-bedrock)
Operator spawns with the working exact ids claude-opus-5 (pi) and claude-sonnet-5 (pi-signed): launch proceeds with the model on the launch line and in meta ✅ pass live 02-fm-spawn-live-after.txt (live-opus, live-sonnetexact)
Operator retries with a suggested provider/id selector: spawn accepts it and real Pi runs it ✅ pass live 02-fm-spawn-live-after.txt (live-provid); 04-real-pi-accepted-ids-run.txt
The separately gated codex-native/&lt;id&gt; --effort ultra pathway still launches ✅ pass live 02-fm-spawn-live-after.txt (live-native)
Ambiguous bare id, colliding selector, unreadable catalog and listed non-Anthropic provider are handled (refuse, suggest only unambiguous selectors, refuse, accept) ⏸️ untested no The prior payload did not establish a live result for this scenario. This machine's real Pi catalog lists only the anthropic provider, so a live run cannot create multi-provider or unreadable-catalog…
  • pi --model sonnet -p ... and pi --model &#39;claude-opus-5[1m]&#39; -p ... against real Pi 0.87.1 (reproduces the incident: 'No API key found for amazon-bedrock' and 'Model "claude-opus-5[1m]" not found')
  • bash drive-live.sh &lt;worktree&gt;: runs the real bin/fm-spawn.sh with the real Pi binary and real HOME answering --help/--list-models; only tmux/treehouse are stubbed. Models: sonnet, claude-opus-5[1m] (pi-signed), amazon-bedrock/us.anthropic.claude-sonnet-5, claude-opus-5, claude-sonnet-5 (pi-signed), anthropic/claude-opus-5-5, codex-native/gpt-6-astra --effort ultra
  • bash drive-live.sh &lt;base 1e8058e archive&gt;: same drive against the pre-change fm-spawn, for comparison
  • pi --model claude-sonnet-5 -p ... and pi --model anthropic/claude-sonnet-5 -p ... (a suggested selector really runs in Pi)
  • bash tests/fm-pi-model-guard.test.sh
  • bash tests/fm-spawn-dispatch-profile.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…hropic

Pi's alias resolver can silently route a bare model like 'sonnet' to an
uncredentialed provider (Amazon Bedrock, 2026-09-25), and a Claude Code
style suffix like 'claude-opus-5[1m]' fails at launch too. Add
pi_model_validate to bin/fm-spawn.sh: it resolves --model against the
selected Pi executable's own '--list-models' catalog and refuses unless
the model matches exactly one listed row whose provider is anthropic,
naming what it resolved to (or that it matched nothing/several things)
and the Anthropic ids that would work. An unreadable or empty catalog is
refused rather than treated as an all-clear. codex-native/<id> stays
exempt: it is the pi-codex-native extension's own runtime-registered
provider, never a --list-models row, and is already gated by
fm-harness.sh's validate_native_effort plus --effort ultra.

The guard sits at fm-spawn.sh's single call site that already resolves
PI_BIN and the final --model for every Pi/pi-signed launch path (crewmate,
scout, secondmate, and relaunch), before any worktree, endpoint, or
metadata is created.

Empirically verified (recorded in the pi harness reference): --list-models
lists only providers with credentials on this machine, so it can prove a
model IS available but never that an unlisted or aliased model is safe.

Two existing dispatch-profile tests used a made-up 'openai-codex/...'
model string for pi/pi-signed purely to exercise --model/--effort
threading; switched those to a real Anthropic id and taught the tests'
fake pi to answer --list-models so the new guard does not block them.

Adds a colocated regression (tests/fm-pi-model-guard.test.sh) pinning the
five required cases with a stubbed catalog. Also manually verified against
the real installed Pi catalog on this machine: 'fm-spawn.sh --harness pi
--model sonnet' refuses with the concrete Anthropic ids, before creating
any worktree or state, and without launching a worker.

Out of scope, left for follow-up: other harnesses' model validation is
unchanged; nothing here checks that a launched worker is actually
processing; config/crew-dispatch.json is untouched.
…/fm-pi-model-guard.test.sh, a portable-serial script, without a duration hint in portable_serial_weight_hints (bin/fm-test-run.sh). That raised the unhinted count to 27 of 176, over the 15% limit, so the coverage guard failed. The guard fails directly in "Test coverage guard" and again inside tests/fm-test-run.test.sh, which is what failed in "Behavior portable parallel 1". Fix: added the line `tests/fm-pi-model-guard.test.sh 17097` to portable_serial_weight_hints, keeping alphabetical order. 17097 ms is that script's measured duration from this PR's own "Behavior portable serial 1" CI job (exit=0). Verified locally: `bin/fm-test-run.sh --check-coverage` passes (serial_unhinted=26); tests/fm-test-run.test.sh exits 0 with all 39 cases ok; tests/fm-pi-model-guard.test.sh passes
@gkalyan
gkalyan merged commit 1e34658 into main Sep 25, 2026
14 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