Summary
provider_used is always "" in sessions.jsonl for all run_skill invocations, even when a provider profile is active. The profile_name (which carries the resolved provider identity) and provider_name (which feeds ProviderOutcome.provider_used) are parallel data channels that never intersect in the run_skill code path.
This makes it impossible to distinguish intentional provider-profile routing from unexpected model drift in post-hoc diagnostics. All MiniMax-routed sessions appear identical to Anthropic-routed sessions in the session log.
Observed across all 8 sessions in kitchen 6de5cec6-c158-46f5-abb4-11c9f25a1435 (implementation pipeline, 2026-05-29).
Root Cause
Data flow trace showing the disconnect:
tools_execution.py:382-389
_resolve_provider_profile() → profile_name_out = "minimax"
provider_name is NEVER SET (defaults to "")
executor.run(..., profile_name=profile_name_out) # line 634
↓
run_headless_core() [headless/__init__.py:100]
Accepts both profile_name (line 128) and provider_name (line 129)
profile_name="minimax" → SkillSessionConfig(profile_name=...) line 173 → env/command only
provider_name="" → _execute_claude_headless(..., provider_name="") line 228
NOTE: profile_name is NOT forwarded to _execute_claude_headless
↓
_execute_claude_headless() [_headless_execute.py:71]
current_provider_name = provider_name = "" # line 140
ProviderOutcome(provider_used=current_provider_name) # lines 541-543
↓
sessions.jsonl: "provider_used": "" ← always blank
The disconnect in one sentence: profile_name carries the resolved provider identity and flows into SkillSessionConfig for subprocess command construction. provider_name is a separate parameter that flows into ProviderOutcome.provider_used for session logging. No code bridges the two in the run_skill path.
current_provider_name can only change via provider fallback (lines 452-458 of _headless_execute.py), which requires provider_name to be non-empty. Since it starts as "", even the fallback path can't update it.
Affected Components
| File |
Line(s) |
Role |
src/autoskillit/server/tools/tools_execution.py |
634 |
Passes profile_name=profile_name_out but never provider_name= |
src/autoskillit/execution/headless/__init__.py |
127-229 |
Separate profile_name and provider_name params; only profile_name populated from run_skill |
src/autoskillit/execution/headless/_headless_execute.py |
140, 541-543 |
current_provider_name always "" from run_skill path |
src/autoskillit/execution/session_log.py |
367, 443, 507 |
Writes empty provider_used to summary.json, token_usage.json, sessions.jsonl |
src/autoskillit/core/types/_type_backend.py |
120-140 |
SkillSessionConfig has profile_name field, consumed only by backend command builders |
Recommended Fix
-
In run_headless_core() (headless/__init__.py), forward profile_name to _execute_claude_headless() as provider_name when provider_name is empty:
effective_provider = provider_name or profile_name
Then pass provider_name=effective_provider to _execute_claude_headless() at line 228.
-
Add an integration test that calls through the run_skill → run_headless_core path with a non-empty profile_name and asserts provider_used is populated in the session log output. The existing tests in test_flush_provider_integration.py call _execute_claude_headless directly with provider_name="minimax", bypassing the run_skill → run_headless_core path where the gap lives.
Historical Context
This is the most deeply recurring issue family in the codebase — 10+ fix commits over 3 months, each addressing one structural escape hatch:
| Commit |
Date |
What it fixed |
202f51c3 |
2026-05-04 |
Added provider_used/provider_fallback fields to SkillResult (no wiring) |
31f487d8 |
2026-05-04 |
Wired provider_used into _build_skill_result (empty by arrival) |
10e26c4e |
2026-05-04 |
Structural immunity via ProviderOutcome typed container (gap hidden by none_used() sentinel) |
682390d9 |
2026-05-28 |
Threaded model_identifier from config resolution to session log flush |
471ce42e |
2026-05-29 |
Fixed argmax heuristic (output-token weighting) |
be6cb848 |
2026-05-29 |
Fixed non-Anthropic provider turn instrumentation |
Pattern: Each fix closes one escape route while the information-flow gap shifts one layer. The architectural root cause — no single authoritative "session intent record" (configured model + configured provider) written atomically at session launch — has never been addressed as a unit. This fix targets the "last mile" consumer: the run_skill call site not forwarding provider_name.
Additional Affected Paths (from adversarial validation)
The same gap exists in two other entry points:
report_bug (server/tools/tools_github.py:619-628): _run_report_session passes profile_name=report_profile_name but never provider_name=. Same disconnect.
- Fleet dispatch (
fleet/_api.py:127-146,607-636): execute_dispatch() has no provider_name parameter at all — it never reaches executor.dispatch_food_truck().
All three paths (run_skill, report_bug, fleet dispatch) should be fixed together since they share the same root cause.
Regression Safety (from adversarial validation)
Forwarding profile_name as provider_name is safe from fallback regression. The provider fallback loop in _headless_execute.py:445-458 requires BOTH provider_name to be truthy AND provider_fallback_env is not None. In the run_skill/report_bug paths, provider_fallback_env is never populated, so remaining_attempts is always 0 — the fallback loop will not activate spuriously.
Test Gap
- Integration tests for
provider_used call _execute_claude_headless directly, bypassing the run_skill → run_headless_core path
- No test verifies
provider_used population through the full run_skill entry point
report_bug and fleet dispatch paths also have no provider_used test coverage
Summary
provider_usedis always""insessions.jsonlfor allrun_skillinvocations, even when a provider profile is active. Theprofile_name(which carries the resolved provider identity) andprovider_name(which feedsProviderOutcome.provider_used) are parallel data channels that never intersect in therun_skillcode path.This makes it impossible to distinguish intentional provider-profile routing from unexpected model drift in post-hoc diagnostics. All MiniMax-routed sessions appear identical to Anthropic-routed sessions in the session log.
Observed across all 8 sessions in kitchen
6de5cec6-c158-46f5-abb4-11c9f25a1435(implementation pipeline, 2026-05-29).Root Cause
Data flow trace showing the disconnect:
The disconnect in one sentence:
profile_namecarries the resolved provider identity and flows intoSkillSessionConfigfor subprocess command construction.provider_nameis a separate parameter that flows intoProviderOutcome.provider_usedfor session logging. No code bridges the two in therun_skillpath.current_provider_namecan only change via provider fallback (lines 452-458 of_headless_execute.py), which requiresprovider_nameto be non-empty. Since it starts as"", even the fallback path can't update it.Affected Components
src/autoskillit/server/tools/tools_execution.pyprofile_name=profile_name_outbut neverprovider_name=src/autoskillit/execution/headless/__init__.pyprofile_nameandprovider_nameparams; onlyprofile_namepopulated from run_skillsrc/autoskillit/execution/headless/_headless_execute.pycurrent_provider_namealways""from run_skill pathsrc/autoskillit/execution/session_log.pyprovider_usedto summary.json, token_usage.json, sessions.jsonlsrc/autoskillit/core/types/_type_backend.pySkillSessionConfighasprofile_namefield, consumed only by backend command buildersRecommended Fix
In
run_headless_core()(headless/__init__.py), forwardprofile_nameto_execute_claude_headless()asprovider_namewhenprovider_nameis empty:Then pass
provider_name=effective_providerto_execute_claude_headless()at line 228.Add an integration test that calls through the
run_skill→run_headless_corepath with a non-emptyprofile_nameand assertsprovider_usedis populated in the session log output. The existing tests intest_flush_provider_integration.pycall_execute_claude_headlessdirectly withprovider_name="minimax", bypassing therun_skill→run_headless_corepath where the gap lives.Historical Context
This is the most deeply recurring issue family in the codebase — 10+ fix commits over 3 months, each addressing one structural escape hatch:
202f51c3provider_used/provider_fallbackfields toSkillResult(no wiring)31f487d8provider_usedinto_build_skill_result(empty by arrival)10e26c4eProviderOutcometyped container (gap hidden bynone_used()sentinel)682390d9model_identifierfrom config resolution to session log flush471ce42ebe6cb848Pattern: Each fix closes one escape route while the information-flow gap shifts one layer. The architectural root cause — no single authoritative "session intent record" (configured model + configured provider) written atomically at session launch — has never been addressed as a unit. This fix targets the "last mile" consumer: the
run_skillcall site not forwardingprovider_name.Additional Affected Paths (from adversarial validation)
The same gap exists in two other entry points:
report_bug(server/tools/tools_github.py:619-628):_run_report_sessionpassesprofile_name=report_profile_namebut neverprovider_name=. Same disconnect.fleet/_api.py:127-146,607-636):execute_dispatch()has noprovider_nameparameter at all — it never reachesexecutor.dispatch_food_truck().All three paths (run_skill, report_bug, fleet dispatch) should be fixed together since they share the same root cause.
Regression Safety (from adversarial validation)
Forwarding
profile_nameasprovider_nameis safe from fallback regression. The provider fallback loop in_headless_execute.py:445-458requires BOTHprovider_nameto be truthy ANDprovider_fallback_env is not None. In therun_skill/report_bugpaths,provider_fallback_envis never populated, soremaining_attemptsis always 0 — the fallback loop will not activate spuriously.Test Gap
provider_usedcall_execute_claude_headlessdirectly, bypassing therun_skill→run_headless_corepathprovider_usedpopulation through the fullrun_skillentry pointreport_bugand fleet dispatch paths also have noprovider_usedtest coverage