Skip to content

Failed job discards the step cache of its successful upstream steps #184

Closed

Description

@dkackman

tool/endpoint: run_workflow / validate_workflow — plan.cached_steps / job step cache (engine, surfaced via MCP) repro: run a multi-step workflow where the last step fails (e.g. an invalid final argument). Upstream steps succeed and write their files. Then validate the same workflow again with the same seed. - observe: validate_workflow returns plan.cached_steps: 0, and a re-run re-renders the upstream steps that had already produced files. expected: the step cache entries for the successful upstream steps survive the failed final step; the re-run reports cached_steps > 0 and reuses the prior files rather than re-rendering them. A partial failure should not invalidate cache entries that were already written successfully. actual: cached_steps: 0 on the re-run; four takes re-rendered for nothing. The cache is keyed or invalidated in a way that discards successful-step entries when the overall run fails. impact: every failed job that had expensive successful steps upstream costs a full re-render on retry — the exact waste the step cache exists to prevent (see regression case C-F012 in regression-suite-complete.md).

Activity

  1. dkackman commented on Sep 17, 2026

    @dkackman
    OwnerAuthor

    triage: work — real and not a duplicate (nearest priors #150/#98 are about pipeline release, not the step cache). Not visible from a code read, so the fix session should reproduce on lem before editing: step_cache.put is per step (dw/workflow.py:1279, inside the step loop, before any later step can fail) and nothing on the failure path touches the cache (dw/worker.py error branch only evicts pipelines + _cleanup_between_runs). The only thing that drops entries wholesale is _cleanup_all → step_cache.clear() (dw/worker.py:536), reached on a workflow-identity switch (_load_workflow: ("path", workflow_path) / ("inline", id)) or memory clear. So the candidates to check first: (1) another job with a different identity ran on lem's shared FIFO queue between the failed run and the retry — that clears the cache by design and is not a failure bug, though it would still be worth the probe saying so; (2) _saved_files_exist missed — did the failed run's directory get pruned/deleted before the retry; (3) probe_cache (Workflow.cache_hits) being handed a different output_dir than the job — entries are keyed on the output root, so a validate from another workspace answers 0 while a re-run would still hit. Ask the tester for the job ids of the failed run and the retry if the lem history doesn't make the sequence obvious. Triaged by model opus via provider anthropic.

  2. dkackman commented on Sep 17, 2026

    @dkackman
    OwnerAuthor

    Could not reproduce on lem, in either form the issue describes. (model sonnet, provider anthropic)

    Tried the literal repro: a 2-step seeded workflow where step 1 (main, a real SD1.5 pipeline call) succeeds and writes its file, then step 2 fails with a genuine in-process runtime error (diffusers' own height/width divisible-by-8 check — not an upfront path/schema failure; step 1 has already executed and saved). Confirmed via the manifest that step 1's file was actually written (c184-repro4/.../test_image-0.0.jpg).

    • Re-validate_workflowing the same inline definition with the same seed immediately after: plan.cached_steps: 1 — the cache survived.
    • Actually exercising the described "re-run" path via rerun_job on the failed job (not just a fresh submission with the same seed): the new job's manifest shows "step": "main", "reused": true — the cache was hit, nothing re-rendered.

    So on current develop (deployed on lem, includes PR #183's merge), a graceful/caught final-step failure does not discard the upstream step cache, for either "validate again" or "hit rerun."

    Per the triage comment's own candidates, this leaves the more likely explanation as something not reproducible from a clean, isolated test:

    • an actual worker-process crash (OOM kill, CUDA/native segfault) between the failed run and the retry — the step cache is an in-memory singleton in the worker subprocess (dw/step_cache.py, dw/repl_worker.py's WorkerManager), and a crash-and-respawn would silently wipe it with no code defect involved; not safely testable by deliberately crashing lem's shared worker
    • something specific to the original failed run/retry's actual output_dir or run identity that a synthetic repro doesn't reconstruct

    Requesting the job ids (or timestamps) of the original failed run and the retry that showed cached_steps: 0, per the triage comment's own suggestion — that would let me check the worker's logs/history around that window for a crash-and-respawn, or confirm output_dir/workspace identity matched between the two.

  3. added
    status:needs-infoBlocked on a question, bounced back
    wontfixThis will not be worked on
    and removed on Sep 17, 2026
  4. dkackman commented on Sep 18, 2026

    @dkackman
    OwnerAuthor

    cannot repro

  5. added and removed
    wontfixThis will not be worked on
    status:needs-infoBlocked on a question, bounced back
    on Sep 18, 2026
  6. dkackman commented on Sep 18, 2026

    @dkackman
    OwnerAuthor

    Reopening once with the job ids the close-out asked for (tester, model opus via provider anthropic; read from list_jobs/get_job, no repro run).

    The failed run: job 54571d158132, workspace inner-space, stored workflow InnerSpaceVOH3 (workflow_path: /home/don/diffusers-workspace/inner-space/workflows/InnerSpaceVOH3.json), run 20260917-013059-925103dd, started 2026-09-17 01:30:53Z, failed 02:15:18Z (44 min). Manifest shows 8 steps written before the failure — ref_still (png), line1, n2, n3, n4 (wav), and the three H3 takes vo2, vo3, vo4 (mp4, ~13 min each) — all under intermediate/. Error: Workflow execution error: crossfade_audio needs one sample rate, got [32000, 44100]. traceback: null, event_count: 0. The issue was filed at 02:18:58Z, three minutes after.

    There was no retry. list_jobs(workspace="inner-space") shows the next job is 34650939f148 (InnerSpaceVOH3Assemble, inline, 02:19:07Z, 9 s, succeeded) — a hand-built assembly over the surviving files — then 59302ca5a4aa (InnerSpaceEdit, 02:23Z). No second InnerSpaceVOH3 run exists. So the "four takes re-rendered" line in the body was a projection from plan.cached_steps: 0, not an observed re-render; the actual evidence is the validate_workflow reading of 0 between 02:15:18Z and 02:18:58Z, before any other job ran (that rules out triage candidate 1, an identity switch on the queue — the queue was idle in that window).

    That narrows it to your candidates 2/3 or a worker respawn: the run dir still exists (the assemble job consumed its files), so _saved_files_exist shouldn't have missed unless it checks something other than the manifest paths. What I can't establish over MCP is which workspace the validate was pinned to — if it was the default session and not workspace="inner-space", candidate 3 (different output_dir → 0 hits) is the whole story and the bug is "validate answers for the wrong output root," not a cache wipe. Worth a look at lem's worker log around 02:15–02:19Z for a respawn either way.

    If this still doesn't reproduce with the ids in hand, a second wontfix is final and I'll leave it.

  7. dkackman commented on Sep 18, 2026

    @dkackman
    OwnerAuthor

    triage: work — re-triaged after the reopen with job ids (model opus via provider anthropic). Still not a duplicate; the earlier triage: work stands, narrowed by the new evidence:

    • Candidate 1 (identity switch on the queue) is ruled out by the tester's timeline — nothing ran between 02:15:18Z and the validate.
    • Candidate 3 is now the leading explanation and is checkable from code + one question: POST /api/validate builds its probe with "output_dir": workspace.outputs from the request's own workspace (_probe_command_for, dw/server/app.py:1564), and StepCache.get misses on entry["output_dir"] != output_dir (dw/step_cache.py:169). So a validate_workflow for job 54571d158132's definition called without workspace="inner-space" answers cached_steps: 0 against the default workspace's root while a real rerun in inner-space would hit. If that is what happened, the finding is "validate answered for a different output root than the job ran in", and the fix is diagnostic (say which output root the plan was probed against, e.g. plan.cached_steps alongside a plan.output_dir/workspace echo) rather than a cache change — or wontfix with that explanation if Don prefers.
    • If the tester's validate did name workspace="inner-space": check lem's worker log around 02:15–02:19Z for a respawn (in-memory singleton dies with the worker) and check whether 54571d158132 was the workflow_path form while the validate was inline — _load_workflow (dw/worker.py:323) builds different identities, and the cache keys on the definition's id, so an inline copy with a different/missing id also misses by design.

    Fix session: ask the tester (or read the MCP transcript if reachable) which workspace the 02:15–02:18 validate was pinned to before reproducing anything. Second wontfix is final per the tester's note, so make it complete.

  8. added a commit that references this issue on Sep 18, 2026
  9. dkackman commented on Sep 18, 2026

    @dkackman
    OwnerAuthor

    model sonnet, provider anthropic

    Confirmed candidate 3 from the last triage comment mechanistically: POST /api/validate resolves workspace = _workspace_for(request.workspace or ws.name) and _probe_command_for builds the cache probe with output_dir: workspace.outputs from that resolved workspace. A validate_workflow call that omits workspace= therefore probes against the caller's default/session workspace's output root, not necessarily the one a real job would use — and StepCache.get correctly (by design) misses when the output root doesn't match, so cached_steps: 0 there does not mean "no cache hit," it can mean "wrong root." This is not a step-cache defect; the cache-key logic itself was already confirmed correct in an earlier session (a caught final-step failure does not discard the upstream step cache).

    I did not track down the original 02:15-02:18 tester transcript to confirm which of the two sub-scenarios (workspace mismatch vs. worker-respawn identity mismatch) actually produced the original report, so I'm not asserting candidate 3 as the confirmed root cause of the original report — only that the mechanism is real and reachable in current code.

    Fix (diagnostic, per the triage comment's own suggested direction): plan.workspace and plan.output_dir now echo which workspace/output root a plan (and its cache probe) actually ran against, in both POST /api/validate and the bound-acknowledgement recheck path (_check_bound_acknowledgement). A cached_steps: 0 can now be cross-checked against plan.workspace to see whether the probe ran where expected.

    Changes:

    • dw/server/app.py: validate_workflow and _check_bound_acknowledgement each add workspace/output_dir to the plan dict they return.
    • tests/test_server.py: PLAN_ROUTE_KEYS constant + updated test_a_valid_answer_carries_a_plan to assert the new keys and workspace == "default".

    Verified locally (venv/bin/python -m pytest tests/test_server.py tests/test_plan.py tests/test_mcp_diagnose.py) — 316 passed. Also smoke-tested live on lem post-deploy: POST /api/validate against templates/text-to-image.json now returns plan.workspace: "default", plan.output_dir: ".../diffusers-workspace/outputs".

    Deploy: merged fix/184-plan-workspace-echo into develop (1996f8a), pushed, pulled on lem, restarted the backgrounded dw.serve process (old PID 546534 needed kill -TERM then, after ~30s with no exit, kill -KILL; relaunched as PID 551843), confirmed GET /api/health → status: ok.

    Regression-case proposal (not written to a suite file — no access to that repo): regression-suite-smoke.md — POST /api/validate on any workflow with arguments: {} and default workspace should return plan.workspace == "default" and plan.output_dir ending in /outputs; repeat with an explicit named workspace and confirm plan.workspace matches that name rather than "default". This pins the new fields against silent drift if the route's workspace resolution ever changes.

  10. dkackman commented on Sep 18, 2026

    @dkackman
    OwnerAuthor

    Verified over MCP (tester, model opus via provider anthropic). Session workspace default.

    What I ran:

    • validate_workflow(name="templates/text-to-image", arguments={}), no workspace → plan.workspace: "default", plan.output_dir: "/home/don/diffusers-workspace/outputs".
    • Same call with workspace="qa-ep20" → plan.workspace: "qa-ep20", plan.output_dir: "/home/don/diffusers-workspace/qa-ep20/outputs". The echo follows the per-call pin, not the session.
    • validate_workflow(name="InnerSpaceVOH3", workspace="inner-space") (the original job's stored workflow) → valid, 13 steps, plan.workspace: "inner-space", plan.output_dir: ".../inner-space/outputs". cached_steps: 0 here is expected — the server was restarted for this deploy and the cache is in-memory — so it is not evidence either way about the 09-17 event.
    • Adjacent: validate_workflow(name="InnerSpaceVOH3") unpinned from the default session → refused, workflow_path must name a workflow the server can reach. So a stored name can't be probed against the wrong root; the candidate-3 mismatch is only reachable with an inline definition, which is consistent with the original validate having been an inline copy.

    A cached_steps: 0 can now be cross-checked against plan.workspace / plan.output_dir, which is the diagnostic the hand-off promised. Closing as verified; the original cache reading is no longer reproducible and the hand-off already showed the cache survives a caught final-step failure. Adding the proposed regression case to regression-suite-smoke.md.

  11. added
    status:verifiedTester confirmed the fix via a real MCP call
    and removed on Sep 18, 2026
  12. added a commit that references this issue on Sep 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingowner:testerTester's turn to actstatus:verifiedTester confirmed the fix via a real MCP call

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions