workflow-fix: GCP H100 queue-wait timeout -> RunPod failover (auto lane)
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
Auto-filed by the workflow-fix-on-bug protocol from a workflow gap observed on task #778 (orchestrator self-observed, user-directed standing rule: "for H100, if GCP is not available we should use RunPod").
Goal
Add a bounded GCP queue-wait timeout to the auto-lane GCP rung so an H100 request that stays PENDING (queued for capacity) fails over to the next lane / the RunPod terminal rung, instead of waiting indefinitely.
Workflow gap
- Bug observed: GCP
FLEX_START(and on-demand) H100 requests that stayPENDINGin the capacity queue have no queue-wait timeout in the auto lane, so the router waits indefinitely instead of failing over. #778'seps-issue-778sat PENDING in the us-central1-b FLEX_START queue ~2h45m+ with no advancement; the run had to be pivoted to RunPod manually. - Why it is a workflow gap: the FREE SLURM lanes already implement this —
FREE_WAIT(default 600s) → PENDING-at-cap → cancel + advance to the next lane (router.py:28). But the GCP rung explicitly has none:router.py:29— "GCP has no queue estimate and no park." So a queued GCP H100 never reaches the RunPod terminal rung the ladder is supposed to fall through to. The existing GCP→RunPod failover fires only on a GCP workload crash (gcp_workload_failover_runpod, #658) or no-capacity exhaustion (RunPodNoCapacityErrorpath), NOT on a long capacity queue where the create succeeded and the instance is PENDING. - Confidence (emitter): high
Proposed change (candidate diff sketch — refine in planning)
- Add a GCP queue-wait timeout knob (e.g.
EPS_GCP_QUEUE_WAIT_SECONDS, default ~600–900s) mirroring the free-laneFREE_WAITsemantics. - In the GCP rung (FLEX_START + on-demand): if the instance does not reach RUNNING within the timeout, CANCEL/DELETE the PENDING instance and advance the ladder — to the next GCP rung if any, else the RunPod terminal rung (
reason: gcp_queue_timeout_failover_runpod). - Do NOT double-count against
MAX_GCP_ATTEMPTS_PER_DAYbeyond the attempts actually made; the cancel is a clean advance, not a workload failure. - Record the timeout + reason on the per-rung
epm:backend-selectedmarker so the trail distinguishes a queue-timeout failover from a workload-crash failover and a no-capacity exhaustion.
Scope / surfaces
- Primary:
src/explore_persona_space/backends/router.py(GCP rung timeout + failover),.claude/rules/compute-backend-failover.md(document the new GCP-queue-timeout → RunPod policy). - Likely also:
src/explore_persona_space/backends/gcp.py(poll/cancel a PENDING instance),tests/test_router.py(pin: GCP PENDING past timeout → cancel → RunPod terminal rung, no infinite wait). - Grep the surface for the pattern before editing (
grep -rniE "FREE_WAIT|flex_start|PENDING|no queue estimate" src/explore_persona_space/backends/ .claude/rules/compute-backend-failover.md) and update every hit.
Constraints / invariants
- Workflow-surface only. Preserve the existing failover invariants + their tests (
tests/test_router.py::test_runpod_is_last_rung_only_after_all_gcp_and_slurm_exhausted,::test_gcp_workload_error_fails_over_to_runpod_no_slurm_cascade). - The queue-timeout failover must remain compatible with the explicit
backend:overrides and the auto lane order; it is an ADDITIONAL advance trigger, not a change to lane precedence. scripts/workflow_lint.pypasses; ruff on touched files passes; keepcompute-backend-failover.md+router.pydocstring + CLAUDE.md consistent.- Runs under
EPM_WORKFLOW_FIX_SESSION=1with aworkflow_fix_target:Provenance line — must NOT auto-route its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: src/explore_persona_space/backends/router.py
- fingerprint: 18dcdaad7a20
target_file: src/explore_persona_space/backends/router.py, .claude/rules/compute-backend-failover.md, src/explore_persona_space/backends/gcp.py, tests/test_router.py bug_observed: GCP FLEX_START/on-demand H100 requests that stay PENDING in the capacity queue have no queue-wait timeout, so the auto lane waits indefinitely instead of failing over (#778 sat PENDING ~3h). why_workflow_gap: the free SLURM lanes have FREE_WAIT (600s) → PENDING-at-cap → cancel + next lane, but router.py:29 states GCP has no queue estimate and no park, so a queued GCP H100 never advances to the RunPod terminal rung. proposed_change: add a bounded GCP queue-wait timeout (env knob) to the FLEX_START/on-demand rung; on timeout cancel the PENDING instance and advance the ladder to the next lane / RunPod terminal rung, mirroring the free-lane FREE_WAIT semantics. diff_sketch: |
- EPS_GCP_QUEUE_WAIT_SECONDS (default ~600-900s)
- in the GCP rung: if not RUNNING within timeout -> cancel PENDING instance -> advance ladder
- (next GCP rung else RunPod terminal rung, reason: gcp_queue_timeout_failover_runpod)
- record timeout+reason on epm:backend-selected marker confidence: high related_task: #778