fix(codegen): clean up orphaned scaffold on cancelled/failed generation - #1252
fix(codegen): clean up orphaned scaffold on cancelled/failed generation#1252groupthinking wants to merge 3 commits into
Conversation
ProjectCodeGenerator performed every scaffolding filesystem call inline inside async def bodies, so a blocked write parked the whole event loop rather than just the requesting coroutine. Move all 28 filesystem calls off the loop, batched into one asyncio.to_thread hop per generator (O(1) hops instead of O(files)). Content generation is pure in-memory string building and stays on the loop; only the writes are offloaded. Output is byte-for-byte identical, verified by loading the pre- and post-change modules side by side and comparing SHA-256 digests of every emitted file plus every returned dict across all three generators. Refs #1250 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Offloading the scaffolding filesystem work to `asyncio.to_thread` (#1250) introduced `await` points that did not exist when the writes ran inline. A request cancelled mid-scaffold now raises `CancelledError` while the worker thread keeps running, so `generate_project` can abandon a `uvai_project_*` directory whose path it never returns and no caller ever cleans up. Reported by CodeRabbit on the scaffolding-off-loop change. Fixes: - Add `_run_offloop`, mirroring `_run_sync_rpc` in cloud_tasks_queue: it shields the worker task and waits for it to settle before propagating cancellation, so higher-level cleanup never races a live writer. - Route all four off-loop hops (mkdtemp + three `_apply_write_plan` batches) through it. - Wrap the post-mkdtemp region so a cancelled or failed generation removes the orphaned scaffold directory (best-effort, off-loop) before re-raising. Adds cancellation/failure-cleanup tests and `_run_offloop` unit tests. Behaviour on the happy path is unchanged; 520 passed across the code generator, video processing, deployment, and AI generator suites. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpREES1d2uL2cq4k7jiezV
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🔍 PR Validation |
|
Important Review skippedAuto reviews are limited based on label configuration. 🏷️ Required labels (at least one) (1)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Repository UI (inherited), Organization UI (inherited) Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
Agent Completion Truth Gate: BLOCKEDReasons: Machine-readable verdict{
"details": {
"invalid_fields": [
"policy.agent_login",
"policy.run_id"
]
},
"reasons": [
"invalid_payload"
],
"verdict": "blocked"
} |
Status at head
|
|
@coderabbitai review This PR resolves the blocking cancellation finding you raised on #1251: a request cancelled mid-scaffold could leak a
Generated by Claude Code |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== PR diff ==='
git diff --find-renames --unified=80 'origin/main...HEAD' -- \
src/youtube_extension/backend/code_generator.py \
tests/unit/test_code_generator.py
printf '%s\n' '=== implementation outline ==='
ast-grep outline src/youtube_extension/backend/code_generator.py --items all --view expanded
printf '%s\n' '=== test outline ==='
ast-grep outline tests/unit/test_code_generator.py --items all --view expanded
printf '%s\n' '=== relevant definitions and call sites ==='
rg -n -C 5 -P '\b(_run_offloop|generate_project|_apply_write_plan)\s*\(' \
src/youtube_extension/backend/code_generator.py \
tests/unit/test_code_generator.py
printf '%s\n' '=== output/equality assertions ==='
rg -n -i -C 4 'byte|equal|equality|expected|write.plan|generate_project' \
tests/unit/test_code_generator.pyLength of output: 56777 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '=== locate focused test file ==='
fd -a -t f '^test_code_generator\.py$' .
printf '%s\n' '=== generate_project and off-loop helper ==='
nl -ba src/youtube_extension/backend/code_generator.py | sed -n '25,265p'
printf '%s\n' '=== current write plans ==='
nl -ba src/youtube_extension/backend/code_generator.py | sed -n '275,460p'
printf '%s\n' '=== baseline write sequences ==='
git show origin/main:src/youtube_extension/backend/code_generator.py \
| nl -ba \
| sed -n '175,445p'
printf '%s\n' '=== test names and cancellation/equality coverage ==='
fd -a -t f '^test_code_generator\.py$' . -x sh -c '
printf "\n--- %s ---\n" "$1"
rg -n -i -C 8 "mkdtemp|cancel|orphan|offloop|byte|equal|verbatim|write.plan" "$1"
' sh {}Length of output: 1202
However, temp_dir = await _run_offloop(tempfile.mkdtemp, prefix="uvai_project_")If cancellation occurs while the Please make the The diff preserves the existing write order and uses equivalent content generation on the normal paths. I could not independently verify the focused test file because ✅ Action performedReview finished.
|
CodeRabbit and Vercel VADE both flagged a remaining leak in the previous commit: the cleanup try/except began only after `temp_dir = await _run_offloop(tempfile.mkdtemp, ...)`. If cancellation landed while the mkdtemp worker was running, the shielded hop still created the directory and then raised CancelledError before the assignment, so the new `uvai_project_*` directory never entered the cleanup scope and leaked. Add `_make_scaffold_dir`, which owns that window: it shields the mkdtemp worker, and on cancellation removes any directory the worker created before propagating CancelledError. `generate_project` now obtains the scaffold path through it, so both the creation window and the write phase are leak-safe. Adds a regression test that blocks mkdtemp, cancels the generation task, releases mkdtemp, and asserts the created directory is gone (prove-fail verified: it fails against the prior leaky path). 87 focused tests pass; ruff clean; mypy adds no new diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpREES1d2uL2cq4k7jiezV
|
Fixed in The mkdtemp hop now owns that window via Verification: added a regression test that blocks The Generated by Claude Code |
|
Superseded by #1251, which carries both defects this PR fixes plus the off-loop conversion of the 28 scaffolding writes. Full rationale and a side-by-side comparison are in #1251 (comment). Summarised: the two branches converged independently on the same shield-and-drain design derived from Credit where due: this PR was ahead of #1251 on the generic-exception path, and that gap was real. It is now closed there in Please reopen if #1251 stalls. |
Canonical issue
Closes #1253.
Outcome
A video-to-software request that is cancelled or fails mid-scaffold no longer leaks a
uvai_project_*temporary directory. The off-loop write hops wait for their worker thread to settle before propagating cancellation (so cleanup never races a live writer), and both themkdtempwindow and the write-plan window remove the scaffold before the error propagates.Scope
src/youtube_extension/backend/code_generator.py_run_offloop— shield-and-wait wrapper (mirrors_run_sync_rpcinservices/cloud/cloud_tasks_queue.py); all three_apply_write_planhops routed through it._make_scaffold_dir— owns themkdtempcancellation window: shields the worker and removes any directory it created before re-raisingCancelledError.generate_project— obtains the scaffold path via_make_scaffold_dirand removes the tree on cancellation/failure.tests/unit/test_code_generator.py— cancellation (both windows) + failure cleanup tests, and_run_offloopunit tests.Risk
shutil.rmtree(..., ignore_errors=True)) and cannot itself fail the request.Verification
Tied to head
a124bde.tests/unit/test_code_generator.py: 87 passed. Both cancellation windows have prove-fail-verified regression tests (each fails against the pre-fix path, passes now).ruff checkclean on both changed files.mypyadds zero new diagnostics (the 13 reported errors are all pre-existing on untouched lines).ISSUE_RESOLVED).agent-completion/truth-gate— see belowProduction evidence
Python-only change on the
POST /api/v1/video-to-softwarerequest path (same endpoint as #1239/#1240/#1251). Not exercised by the Next.js Vercel preview; no runtime behaviour change on success — the new cleanup path is only reached on cancellation/failure. Vercel production remains READY.Agent handoff
agent-completion/truth-gatemainAgent provenance
The remaining red check is
agent-completion/truth-gate(invalid_payload— missing agent-lock provenance manifest). This branch isclaude/*, so the gate classifies the PR as agent-authored and requires a frozen pre-dispatch intent plus a trusted terminal agent result registered through orchestration. I have deliberately not hand-filled that manifest to force the gate green: doing so would fabricate provenance the gate exists to verify, which repo policy forbids (REAL_MODE_ONLY; "do not weaken or impersonate the gate", per #810). Disposition of that gate and the merge to protectedmainare left to human sign-off.🤖 Generated with Claude Code
https://claude.ai/code/session_01UpREES1d2uL2cq4k7jiezV