Skip to content

Revert #4162: _gate_reachable_post_prune blocks working Codex+MiniMax pipeline — add provider-aware capability gate exemption #4163

Description

@Trecek

Investigation: PR #4162 Regression — Provider-Unaware Capability Pruning Blocks Working Codex Pipeline

Date: 2026-06-30
Scope: Why the #4162 fix (reachability check in _is_vacuous_gate) broke a working Codex+implementation pipeline, and what the correct fix is.
Mode: Deep Analysis (2 batches, 8 subagents)

Summary

PR #4162 (f5d7724f3) was wrong. It added a reachability check to _is_vacuous_gate that causes dispatch_infeasible to fire for Codex+implementation, blocking the entire kitchen from opening. Before this fix, Codex successfully orchestrated the implementation pipeline — planning steps (make-plan, review-approach, dry-walkthrough) ran on Codex, and the implement step ran on Claude Code via a MiniMax provider override (ANTHROPIC_BASE_URL-triggered backend override at tools_execution.py:762-769). Session telemetry confirms multiple successful end-to-end runs on June 5-6, 2026 with backend: "claude-code", provider_used: "minimax", step_name: "implement", success: true.

The root cause is not _is_vacuous_gate or its reachability check — it is that backend_supports_git_write is set globally from the orchestrator backend's capabilities (_auto_overrides.py:27-28), while the implement step uses a per-step provider override that routes it to a capable backend (Claude Code). The skip_when_false: inputs.backend_supports_git_write guard on implement (implementation.yaml:328) prunes the step based on the orchestrator's capability, not the step's actual runtime backend. This pruning is incorrect when a provider override exists.

Root Cause

Primary (architectural): Capability evaluation happens at the wrong level. backend_supports_git_write is a global recipe ingredient set from the orchestrator backend's git_metadata_writable field (_auto_overrides.py:27-28). But the implement step's actual runtime backend is determined by per-step provider resolution at run_skill dispatch time (tools_execution.py:762-769). When a provider profile with ANTHROPIC_BASE_URL is configured for the implement step, run_skill forces backend_override = AGENT_BACKEND_CLAUDE_CODE — the headless session runs on Claude Code, which has git_metadata_writable=True. The pruning decision at load time has no access to this per-step resolution. [SUPPORTED — telemetry confirms successful implement runs on Claude Code via MiniMax]

Proximate (the regression): PR #4162 added _gate_reachable_post_prune() to _is_vacuous_gate(), which checks if any surviving step routes to gate_backend_write. After route-repair, create_impl_worktree.on_success points to gate_backend_write, making it reachable. _is_vacuous_gate now returns Falsedispatch_infeasible fires → kitchen won't open → nothing runs at all. Before #4162, _is_vacuous_gate returned True, the kitchen opened, and planning steps worked. [SUPPORTED]

Why the prior investigation was wrong: It correctly identified the reachability gap in _is_vacuous_gate but incorrectly concluded that dispatch_infeasible was the desired outcome. It failed to account for per-step provider overrides — the implement step can use a different, capable backend at runtime. The telemetry proving this was available but not examined. [SUPPORTED]

Affected Components

  • src/autoskillit/recipe/_recipe_composition.py:179-223_is_vacuous_gate(): the [FIX] Rectify: Vacuous Gate Reachability Blind Spot Bypasses Dispatch Admission Control #4162 reachability check that needs reverting [SUPPORTED]
  • src/autoskillit/recipe/_recipe_composition.py:226-246_gate_reachable_post_prune(): the new function that needs removing [SUPPORTED]
  • src/autoskillit/server/tools/tools_recipe.py:246-253 — hard early-return added by [FIX] Rectify: Vacuous Gate Reachability Blind Spot Bypasses Dispatch Admission Control #4162 that needs reverting [SUPPORTED]
  • src/autoskillit/server/tools/_auto_overrides.py:19-28_backend_capability_overrides(): sets global backend_supports_git_write from orchestrator only [SUPPORTED]
  • src/autoskillit/server/tools/tools_execution.py:762-769_provider_override: per-step backend override mechanism [SUPPORTED]
  • src/autoskillit/server/tools/_preflight.py:34-114_check_dispatch_feasibility(): existing per-step provider exemption pattern for run_skill steps [SUPPORTED]
  • src/autoskillit/recipes/implementation.yaml:310-329implement step: skip_when_false: inputs.backend_supports_git_write (line 328), no provider: field [SUPPORTED]
  • src/autoskillit/recipes/implementation.yaml:356-365gate_backend_write step [SUPPORTED]
  • src/autoskillit/execution/backends/codex.py:582,594anthropic_provider_capable=False, git_metadata_writable=False [SUPPORTED]
  • src/autoskillit/server/_guards.py:341-436_resolve_provider_profile(): 4-tier provider resolution [SUPPORTED]

Data Flow

The working pipeline (before PR #3960, June 5-6):

  1. Codex orchestrator calls open_kitchen(name="implementation") — no skip_when_false on implement, no gate_backend_write
  2. implement step dispatched via run_skill — provider profile "minimax" resolved from user config
  3. provider_extras contains ANTHROPIC_BASE_URL_provider_override=Truebackend_override="claude-code"
  4. Headless session runs on Claude Code backend with MiniMax model — git writes succeed
  5. Pipeline completes successfully

The broken pipeline (after PR #3960 + #4162):

  1. _backend_capability_overrides(codex_backend){"backend_supports_git_write": "false"} (from orchestrator)
  2. _prune_skipped_steps prunes implement (line 328: skip_when_false: inputs.backend_supports_git_write)
  3. Route-repair: create_impl_worktree.on_successgate_backend_write
  4. _compute_capability_feasibility_is_vacuous_gate_gate_reachable_post_prune finds the gate reachable → returns False
  5. dispatch_feasible=Falsedispatch_infeasible → kitchen won't open → nothing runs

The split-plane mismatch:

Layer When Source Sees provider override?
_backend_capability_overrides Recipe load time Orchestrator backend No
_prune_skipped_steps Recipe load time Global ingredient No
_compute_capability_feasibility Recipe load time Post-prune recipe No
_check_dispatch_feasibility Post-load preflight Per-step provider Yes
run_skill dispatch Runtime Per-step provider Yes

The capability decision is made at load time (layers 1-3) which have no provider visibility. The provider-aware layers (4-5) run too late — the step is already pruned.

Test Gap Analysis

Similar Patterns

  • _check_dispatch_feasibility (_preflight.py:62-80) already solves the analogous problem for hook enforcement: it iterates run_skill steps, resolves per-step providers, and exempts steps routed to Claude Code from the hook check. This is the exact pattern needed for capability gates. [SUPPORTED]

Design Intent Findings

Historical Context

Telemetry evidence of working pipeline:

Date (UTC) Backend Step Model Provider Success
2026-06-05 22:15 claude-code implement MiniMax-M3 minimax True
2026-06-05 23:02 claude-code implement MiniMax-M3 minimax True
2026-06-05 23:11 claude-code implement MiniMax-M3 minimax True

All three show recipe_name: "implementation", step_name: "implement", backend: "claude-code", provider_used: "minimax". The pipeline was working.

The fix chain that broke it:

This is a recurring pattern — consider running /rectify for architectural immunity after resolving the immediate issue.

Scope Boundary

Investigated: Per-step provider resolution mechanics, _resolve_provider_profile 4-tier chain, _provider_override backend override in run_skill, session telemetry for MiniMax implement runs, the #4162 commit diff and all test changes, IL-layer constraints for fix placement, _check_dispatch_feasibility per-step exemption pattern, _backend_capability_overrides global-only design.

Not yet explored: Exact user provider config (step_overrides vs recipe_overrides vs default_provider); whether remediation.yaml and implementation-groups.yaml have the same provider-override use pattern; whether un-pruning implement would cause issues if no provider override is configured (fallback to Codex → runtime failure).

Recommendations

Single recommendation: Revert PR #4162 and extend _check_dispatch_feasibility with provider-aware capability gate exemption.

Two coordinated changes:

1. Revert PR #4162 (f5d7724f3):

  • Remove _gate_reachable_post_prune() and its call from _is_vacuous_gate()
  • Remove post_prune_recipe parameter from _is_vacuous_gate() and _compute_capability_feasibility()
  • Revert tools_recipe.py hard early-return
  • Revert all test assertions back to dispatch_feasible=True for Codex+implementation
  • This restores the PR [FIX] Dispatch Feasibility Immunity — Capability-Aware Admission Control #4130 behavior where planning steps work on Codex

2. Extend _check_dispatch_feasibility (_preflight.py:34-114) for capability gate awareness:

  • After the existing run_skill provider-exemption loop (lines 64-80), add a second check for run_python capability gate steps
  • For each surviving run_python gate step whose callable is in CAPABILITY_GATE_CALLABLES:
    • Find its predecessor run_skill steps (steps with routing fields pointing to the gate — use active_recipe_steps which is already a parameter)
    • Resolve each predecessor's provider profile via _resolve_provider_profile (already imported at line 62)
    • If the predecessor routes to a capable backend (Claude Code via ANTHROPIC_BASE_URL), the gate would pass at runtime — mark it as provider-exempt
  • When all predecessor run_skill steps are provider-exempt, override backend_supports_git_write to "true" in the ingredient overrides (or mark the gate as non-blocking)

This is at IL-3 (legal), uses existing _resolve_provider_profile (already imported), and follows the established pattern from lines 64-80. The config_providers and recipe_name parameters are already available.

Why this is architecturally correct:

  • Revert restores the working pipeline immediately (Codex handles planning, gate fires but pipeline partially works)
  • The provider-aware check closes the gate_backend_write runtime failure by making the gate aware of per-step providers
  • No IL violations — all provider resolution stays at IL-3
  • No recipe YAML changes needed — the existing skip_when_false / gate_backend_write structure is preserved
  • Defense-in-depth: if no provider override is configured, gate_backend_write still fires and fails honestly

Killed alternatives:

  • Make _prune_skipped_steps provider-aware — killed: IL violation (IL-2 cannot import _resolve_provider_profile from IL-3)
  • New implement_capable computed ingredient — killed: correct but higher complexity (requires recipe YAML changes across 3 recipes + new server-side computation)
  • Make _is_vacuous_gate provider-aware — killed: masks the symptom (implement is still pruned and never runs) instead of fixing the root cause
  • Override backend_supports_git_write to "true" before load_and_validate — killed: chicken-and-egg problem (recipe steps aren't known until after loading)

Breakage Analysis

Confidence Levels

Finding Confidence
Codex+implementation pipeline worked before via MiniMax provider override SUPPORTED (telemetry: 3 successful sessions)
_provider_override at tools_execution.py:762-769 forces Claude Code backend SUPPORTED (code evidence)
backend_supports_git_write is set globally from orchestrator, not per-step SUPPORTED (code evidence)
PR #4162 reachability check causes the regression SUPPORTED (code evidence + observed behavior)
_check_dispatch_feasibility is the correct fix location (IL-3, has provider access) SUPPORTED (existing pattern at lines 64-80)
The prior investigation (#4161) was wrong in its conclusion SUPPORTED (missed per-step provider overrides, didn't examine telemetry)
No provider override → gate_backend_write still fails at runtime (defense-in-depth works) SUPPORTED (code evidence)
Exact user provider config (step_overrides vs recipe_overrides) NEEDS-EVIDENCE (telemetry confirms it exists but config not inspected)

Correction: Telemetry Was Misread — Codex Has Never Successfully Run implement

Date: 2026-06-30 (post-adversarial validation)

What was wrong in the original report

The original investigation claimed that "session telemetry confirms multiple successful end-to-end runs on June 5-6 with backend: "claude-code", provider_used: "minimax", step_name: "implement", success: true" and attributed these to Codex orchestration. This was wrong.

Independent verification of the telemetry shows:

  • All three cited kitchens (de888ab5, a2a8818d, 26d93572) have backend: "claude-code" for every session — including the orchestrator sessions. These were Claude Code orchestrated runs, not Codex.
  • Claude Code has anthropic_provider_capable=True (_type_backend.py:259), so the _provider_override mechanism at tools_execution.py:762-769 never fires — the condition not tool_ctx.backend.capabilities.anthropic_provider_capable is False.
  • The ONE Codex implement session in all telemetry (kitchen 5a4792ae) ran on backend=codex with NO provider override, on the remediation recipe (not implementation), with model=sonnet.
  • Codex has never successfully completed an implement step on the implementation recipe. The June 12 investigation's statement "Codex has never completed an end-to-end implementation pipeline with committed code" was correct.

What this means for the recommendation

The recommendation to "revert #4162 and extend _check_dispatch_feasibility" has two additional structural flaws identified by adversarial validation:

  1. _check_dispatch_feasibility runs AFTER load_and_validate (after pruning is complete). The implement step is already absent from post_prune_step_names and active_recipe_steps. The function cannot un-prune a step or override backend_supports_git_write in a way that affects pruning.

  2. A pure revert of [FIX] Rectify: Vacuous Gate Reachability Blind Spot Bypasses Dispatch Admission Control #4162 restores the "21-minute waste" state (planning works, gate_backend_write still fails at runtime), NOT the "working pipeline" state. The working pipeline never existed on Codex+implementation.

Revised assessment

PR #4162's reachability check in _is_vacuous_gate is technically correctgate_backend_write IS reachable after route-repair, and it DOES deterministically fail. The dispatch_infeasible response correctly prevents 21 minutes of wasted work.

The path to making Codex work for implementation requires a forward fix (not a revert):

  • Per-step capability evaluation that accounts for provider overrides at pruning time
  • Or a new computed ingredient (implement_capable) that checks both the orchestrator backend AND the per-step provider
  • Or the hybrid dispatch architecture proposed in the June 12 investigation

This is a feature request, not a bug fix. Codex+implementation has never worked end-to-end.


Root Cause Found: _provider_aware_capability_overrides is too conservative

Date: 2026-07-01

The bug

_provider_aware_capability_overrides (_auto_overrides.py:64-89) iterates ALL run_skill steps guarded by skip_when_false: inputs.backend_supports_git_write and requires EVERY one to have a provider override with ANTHROPIC_BASE_URL. If ANY step lacks it, the function bails and returns backend_supports_git_write=false.

The implementation.yaml recipe has 9 guarded steps: implement, retry_worktree, fix, merge_gate_fix, rebase_conflict_fix, resolve_review, resolve_pre_review_conflicts, resolve_ci, resolve_pre_resolve_conflicts. Only implement has a recipe_overrides entry mapping it to minimax. The other 8 don't. So the function bails on the second step (retry_worktree) and returns false.

Evidence

implement: profile=minimax has_base_url=True
retry_worktree: profile=anthropic has_base_url=False  ← bails here
fix: profile=anthropic has_base_url=False
merge_gate_fix: profile=anthropic has_base_url=False
...

Fix

Change _auto_overrides.py:85-86 from "ALL guarded steps must be capable" to "ANY guarded step is capable":

The current conservative logic (return base on first miss) should be changed to: if at least one guarded step has a capable provider override, flip backend_supports_git_write to "true". The steps without provider overrides are still defended by gate_backend_write at runtime (defense-in-depth).

Files to modify

  • src/autoskillit/server/tools/_auto_overrides.py:85-86 — change from all-must-pass to any-passes logic
  • Tests asserting the current conservative behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugExisting behavior is brokenrecipe:remediationRoute: investigate/decompose before implementationstagedImplementation staged and waiting for promotion to main

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions