Skip to content

Codex backend dispatch_infeasible blocks entire implementation recipe — hybrid dispatch via _preflight.py extension #4109

Description

@Trecek

Problem

The Codex backend cannot run any implementation-family recipe (implementation, remediation, implementation-groups). open_kitchen returns dispatch_infeasible with infeasible_steps: ["gate_backend_write"] before any pipeline step executes. The error: "backend 'codex' causes steps ['gate_backend_write'] to route to terminal failure. Use a backend with git_metadata_writable=True (e.g. claude-code)."

This is by design — the capability admission control (PR #4094, cfb6ecc51) correctly detects that the pipeline is dead-on-arrival — but the design leaves no path forward for Codex users who want the full implementation workflow. The Codex backend is classified as FeatureLifecycle.EXPERIMENTAL (core/types/_type_constants_features.py:43-52) with default_enabled=False, and its session success rate for non-write steps is 93.2% (55/59), yet the implementation recipe is completely inaccessible.

Observed behavior

$ AUTOSKILLIT_FEATURES__CODEX_BACKEND=true \
  AUTOSKILLIT_AGENT_BACKEND__BACKEND=codex \
  autoskillit order
# Select "implementation" recipe
→ {"success": false, "kitchen": "dispatch_infeasible",
   "infeasible_steps": ["gate_backend_write"]}

The kitchen gate is disabled, all kitchen tools are hidden, and the user cannot proceed with any recipe step — not even plan or verify.

Expected behavior

The implementation recipe should either:

  1. Open with a degraded pipeline (plan + verify + review steps only, git-write steps pruned), OR
  2. Support hybrid dispatch where git-write steps fall back to Claude Code while Codex handles analysis steps

Root Cause Chain

  1. OS constraint: codex exec --sandbox workspace-write blocks .git/ writes via Linux landlock/bubblewrap. The danger-full-access mode that allows .git/ writes is rejected by Codex CLI when approval_policy=never (required for headless dispatch). The user's terminal showing "permissions: YOLO mode" reflects the interactive TUI — AutoSkillit's headless sessions use codex exec, which cannot use YOLO mode. (codex-rs/protocol/src/permissions.rs: forbidden_agent_metadata_write() returns None only for Unrestricted sandbox kind)

  2. Capability declaration: CodexBackend.capabilities.git_metadata_writable = False — hardcoded, unconditional (execution/backends/codex.py:587)

  3. Ingredient injection: _backend_capability_overrides() returns {"backend_supports_git_write": "false"} (server/tools/_auto_overrides.py:27-28; fleet mirror at fleet/_api.py:119-120); callers inject the result into recipe ingredient overrides

  4. Step pruning: _prune_skipped_steps() removes 11 git-write step occurrences with skip_when_false: inputs.backend_supports_git_write but gate_backend_write survives (no skip_when_false guard) (recipe/_recipe_composition.py:348-469)

  5. DOA detection: _compute_capability_feasibility() finds gate_backend_write surviving post-prune with on_exhausted absent → RecipeStep dataclass default populates "escalate" (schema.py:124) → DOA classified (recipe/_recipe_composition.py:155-158)

  6. Kitchen refused: _dispatch_infeasible_response() disables gate and kitchen tools (tools_kitchen.py:128-155)

Prescribed Change: Hybrid Dispatch via Provider-Override Exemption

Critical architectural constraint

The existing _check_dispatch_feasibility() in _preflight.py:34-114 cannot be the integration point for this change. In tools_kitchen.py, the dispatch_feasible guard at line 715 (deferred-recall path) and line 828 (normal path) returns early with _dispatch_infeasible_response before _check_dispatch_feasibility is called at line 722/836. Any extension to _preflight.py would be dead code for this use case.

The fix must inject provider-override awareness upstream of the dispatch_feasible guard — either:

  • (a) In _compute_capability_feasibility (recipe/_recipe_composition.py:109-160) via a new parameter bridging IL-2/IL-3 (e.g., steps_with_backend_override: frozenset[str] computed by the IL-3 caller)
  • (b) In tools_kitchen.py itself, as a new provider-override check between the load_and_validate call and the dispatch_feasible guard — overriding dispatch_feasible to True when provider overrides route git-write steps to a capable backend
  • (c) In recipe/_api.py at the load_and_validate call site, passing a capability_override_map derived from backend_name that _compute_capability_feasibility can consult

Option (b) has the lowest blast radius — it requires no IL-2 changes and no schema changes:

Implementation (Option b)

  1. Add a provider-override exemption check in tools_kitchen.py between the load_and_validate return and the dispatch_feasible guard. When dispatch_feasible=False and infeasible_steps contains only capability-gate steps (CAPABILITY_GATE_CALLABLES), check whether the steps guarded by those gates have provider overrides that would route to a git_metadata_writable=True backend via the existing backend_override mechanism at tools_execution.py:761-769

  2. If all infeasible gate steps have capable provider overrides, set dispatch_feasible=True in the result dict and proceed to open the kitchen — the pipeline will succeed at runtime via the backend_override mechanism

  3. Config validation: If provider overrides are declared but misconfigured (Claude Code unavailable, wrong ANTHROPIC_BASE_URL), the exemption should not apply — the fail-closed guarantee must be preserved

  4. Fallback path: When no provider overrides are configured, allow opening the kitchen with a degraded pipeline (plan + verify + review only) instead of refusing entirely. This requires NOT calling gate.disable() / disable_components() — the gate stays open, the pruned recipe is served, and gate_backend_write fires at runtime as defense-in-depth, routing to release_issue_failure

  5. Apply the same logic on the deferred-recall path (tools_kitchen.py:715-718) and verify dispatch_food_truck (tools_fleet_dispatch.py) also checks dispatch_feasible from load_and_validate (gap analyst confirmed it currently does NOT — it silently ignores the field)

Files affected

File Change
src/autoskillit/server/tools/tools_kitchen.py Provider-override exemption check before dispatch_feasible guard (both normal and deferred-recall paths)
src/autoskillit/server/tools/tools_fleet_dispatch.py Add dispatch_feasible check (currently missing — fleet dispatch silently ignores DOA result)
src/autoskillit/recipes/implementation.yaml No YAML changes needed
src/autoskillit/recipe/_recipe_composition.py No changes — DOA detection stays as-is for defense-in-depth

Relationship to #4090

Issue #4090 prescribes a purpose-step annotation approach with schema changes. PR #4094 (which references #4090) implemented a different, simpler mechanism — CAPABILITY_GATE_CALLABLES + _compute_capability_feasibility — without any RecipeStep schema changes. #4090 remains open in GitHub despite its core admission-control goal being substantially addressed by #4094. This issue adds the hybrid dispatch escape hatch that neither #4090 nor #4094 provide.

Relationship to #4083

Issue #4083 covers hook-class dispatch feasibility (addressed by PR #4087's _check_dispatch_feasibility). This issue covers capability-class dispatch feasibility — the same "claim-before-validate" anti-pattern but a different capability class. The mechanism is different: #4087 fires at _check_dispatch_feasibility (post dispatch_feasible guard); this issue must fire pre-dispatch_feasible guard.

What Works Today on Codex (for context)

Step Sessions Success Rate Notes
plan 17 94% (16/17) 1 early failure fixed in current version
verify 18 83% (15/18) 3 zero_writes on dry-walkthrough output detection
audit_impl 8 100% (8/8)
review_pr 4 100% (4/4)
run_diagnostic 4 100% (4/4)
implement/fix/merge/PR 0 N/A Blocked by sandbox — never attempted

Open Issue Status

Tests

  1. open_kitchen with Codex + implementation recipe + provider overrides routing git-write steps to Claude Code → kitchen opens, dispatch_feasible=True
  2. open_kitchen with Codex + implementation recipe + NO provider overrides → degraded pipeline opens (plan/verify/review only) instead of dispatch_infeasible
  3. Provider-override exemption with falsy capability ingredient + capable provider override → dispatch_feasible overridden to True
  4. Provider-override exemption with falsy capability ingredient + NO provider override → dispatch_feasible remains False, degraded-mode fallback
  5. Provider-override exemption with misconfigured provider override (wrong ANTHROPIC_BASE_URL) → dispatch_feasible remains False, fail-closed preserved
  6. dispatch_food_truck with Codex + implementation recipe → add dispatch_feasible check (currently missing)
  7. Deferred-recall path (tools_kitchen.py:715-718) with provider override exemption → same behavior as normal path
  8. Gate state after degraded-mode opening → gate remains enabled (NOT disabled), kitchen tools visible
  9. Existing test_codex_implementation_dispatch_infeasible (test_bundled_recipes_dispatch_ready.py:268-281) updated to reflect new degraded-mode behavior

Acceptance Criteria

  1. Codex users can open the kitchen with the implementation recipe (either in degraded mode or with hybrid dispatch configured)
  2. gate_backend_write is retained as runtime defense-in-depth
  3. No IL violations introduced
  4. Fail-closed guarantee preserved when provider overrides are not configured or misconfigured
  5. ingredients_only=True discovery flows continue to function
  6. dispatch_food_truck checks dispatch_feasible (fleet gap closed)
  7. Both open_kitchen paths (normal and deferred-recall) apply the exemption

Investigation

Full investigation report: .autoskillit/temp/investigate/investigation_codex_dispatch_infeasible_2026-06-12_151200.md

Deep analysis mode: 3 exploration batches (9 subagents), adversarial challenge round, solution convergence with blast radius analysis, 3-validator post-report validation. All findings SUPPORTED by direct code evidence except hybrid dispatch architecture (NEEDS-EVIDENCE — architecture not yet validated).

Post-filing adversarial validation: 2 independent agents fact-checked and gap-analyzed this issue body. Corrections applied: line numbers for backend_override mechanism (761-769, not 731-754), step count (11 occurrences, not 9), session statistics (55/59 = 93.2%, not 55/60 = 91.7%), open-issue status accuracy (PRs address but don't close the GH issues), and critical architectural finding that _check_dispatch_feasibility is unreachable for this use case (fires after the dispatch_feasible early-return).

Metadata

Metadata

Assignees

No one assigned

    Labels

    recipe: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