Repository navigation
Pi sub-agent model.resolution stays 'exact' when the model fails the /reflect liveness check #70384
Description
Problem
When a Pi sub-agent model fails Pi's /reflect liveness check, the unified session still records that sub-agent's model.resolution as successful. Pi aborts startup, but usage/aw_session.jsonl says the model resolved.
Seen in https://github.com/github/gh-aw/actions/runs/39325995892 (Smoke Pi Routed, workflow_dispatch, 19 Oct 2026, branch copilot/add-session-contract-checks from #70294, head 893ffe1377ec1997588ac17dfdbbcb8ecb499a3f).
Evidence
Audited with a dev build of main (gh aw dev-b68bd31219): gh aw audit 39325995892 --no-baseline --json.
smoke-pi-routed.mddeclares the inline sub-agentmini-whoamiwithmodel: copilot/gpt-5.4-mini. Copilot no longer offersgpt-5.4-mini; [model-inventory] Model alias inventory - action required - 2026-10-19 #70367 lists this pin asmodel_unavailable.- The audit reports
model_unavailable(high): Pi startup rejected model "gpt-5.4-mini" on provider "copilot" before inference. Same-run awf-reflect.json confirms models_fetch_complete=true; the configured copilot endpoint advertises 36 model(s), but not "gpt-5.4-mini". - The
/reflectcapture (sandbox/firewall/awf-reflect.jsonin the agent artifact) hasmodels_fetch_complete: trueand one configured endpoint,copilot, with 36 models. It includesclaude-haiku-4.5andgpt-5.6-luna, but notgpt-5.4-mini. agent-stdio.log(the main model was routed:inference routing: mode=awf-routed model=gpt-5.6-luna):The agent made no model requests and exited with code 1.sub-agent haiku-whoami: requested=copilot/claude-haiku-4.5 resolved=github-copilot/claude-haiku-4.5 sub-agent mini-whoami: requested=copilot/gpt-5.4-mini resolved=github-copilot/gpt-5.4-mini awf-reflect: model availability confirmed (provider=copilot, model=claude-haiku-4.5) warning: awf-reflect model availability check failed (provider=copilot, model=gpt-5.4-mini) fatal: Pi model "gpt-5.4-mini" is not advertised by the configured copilot proxy endpoint; The requested model is not supported: 'copilot/gpt-5.4-mini' is not available in the workflow's live model catalog. ... [pi-harness] attempt 1: process exit event exitCode=1- Despite that failure,
agent/model-resolution.jsonand the session'smodel.resolutionevents contain:{"engine":"pi","role":"mini-whoami","configured_model":"copilot/gpt-5.4-mini","resolved_model":"gpt-5.4-mini","provider":"copilot","status":"exact","cause":null} - The smoke's U3 check (Add unified-session contract checks (U1–U3) to smoke assertions #70294) passed on that record while E1, R3, R4, S1–S3 and U1 failed:
PASS U3 model.resolution recorded for haiku-whoami: copilot/claude-haiku-4.5 -> claude-haiku-4.5 (exact), mini-whoami: copilot/gpt-5.4-mini -> gpt-5.4-mini (exact)
Cause
Line numbers below are from main at b68bd31219.
actions/setup/js/pi_subagent_config.cjs(preparePiSubagents, ~L87) records the sub-agent's resolution (recordModelResolution(... status: resolution.status ...)) as soon asresolveConfiguredModelreturns. For a non-alias ID, that step returnsexactwithout checking whether the model is live.actions/setup/js/pi_models_json.cjs(sub-agent loop, ~L538) then callsvalidatePiModelAvailabilityfor each sub-agent model. That call throws aModelAliasResolutionErrorwithreason: "model_unavailable"for a model the endpoint doesn't list, but it doesn't replace the earlierexactrecord.- The main model path in the same file (~L445–452) already handles this case. It wraps
validatePiModelAvailabilityin a try/catch, recordsstatus: "failed",cause: error.reason,resolved_model: null, and then rethrows. The sub-agent path has no such handling.
Proposed solution
Make the sub-agent path match the main path. In the sub-agent loop in pi_models_json.cjs, when validatePiModelAvailability throws a ModelAliasResolutionError:
- Record that sub-agent role's
model.resolutionwithstatus: "failed",cause: error.reason(model_unavailable),resolved_model: nullandprovider: error.provider || null. Use the sameconfigured_modelthatpreparePiSubagentsrecorded.recordModelResolutionreplaces records with the sameengineandrole(resolve_model_alias.cjs~L687), so the earlierexactrecord goes away. - Rethrow the error.
Do not change the main-model path or the exact meaning used by the shared resolver (resolveConfiguredModel).
Implementation notes (from reading the code):
- Don't copy the main path's
!routingRequiredguard. This run was routed, and the sub-agent records were still written. The failed sub-agent record has to be written in both routed and unrouted runs. - The loop skips repeated model IDs (
seen). If two sub-agents use the same dead model, only the first one gets checked. Mark every sub-agent role that uses the failingmodelIdas failed. Otherwise the second role keeps itsexactrecord.
Acceptance cases
Add these tests to actions/setup/js/pi_models_json.test.cjs, next to fails closed with model_unavailable for a retired exact id. Build the fixture from the real /reflect capture of run 39325995892: the agent artifact's sandbox/firewall/awf-reflect.json, which doesn't list gpt-5.4-mini. Store it with the existing fixtures, for example actions/setup/js/fixtures/awf-reflect-pi-routed-39325995892.json. Keep models_fetch_complete and endpoints exactly as captured; don't invent a reflect shape.
records failed model_unavailable for a sub-agent pinned to a model missing from /reflect (run 39325995892): a sub-agent pinned tocopilot/gpt-5.4-miniwith that/reflectdata. Pi startup still fails as it does today. The role'smodel.resolutionisfailed/model_unavailable/resolved_model: null, and noexactrecord remains for that role.keeps exact resolution for a sub-agent on a live model: a sub-agent oncopilot/claude-haiku-4.5is unchanged and staysexact.marks only the unavailable sub-agent failed when one of two sub-agents is live: one live and one dead sub-agent. The live one staysexact, the dead one becomesfailed, and startup still fails. Test both declaration orders, because the loop stops at the first failure.- Main-model failure path unchanged: the existing main-path tests, including
fails closed with model_unavailable for a retired exact id, still pass without changes.
End to end
On the PR branch, run smoke-pi-routed. It's still pinned to gpt-5.4-mini until that pin is replaced separately. Expected result: it fails at startup, its session shows mini-whoami as failed / model_unavailable, and U3 now fails and reports the cause, for example FAIL U3 role mini-whoami status is failed (cause model_unavailable), expected exact or resolved .... List the run URL in the PR.
Scope
- Keep the PR to this one fix. Don't change files or behavior unrelated to it.
- Before fixing a failing check, compare it with the same check on
main. If it also fails onmain, don't fix it in this PR. Mergemainoncemainis fixed, and note in the PR which checks are known failures onmain. - This applies even if a bot comment asks you to fix a failing check: fix only failures caused by this PR's changes.
- Don't change any workflow's model pin. Dead pins, including
smoke-pi-routed'smini-whoami: copilot/gpt-5.4-mini, are tracked in [model-inventory] Model alias inventory - action required - 2026-10-19 #70367. - Don't change the smoke assertions (
smoke_model_routing_assertions.cjs, including U1–U3).
Activity