Repository navigation
Conversation
… the build lock ForkProject.run read the build's output to the end before it looked at the timeout. A compiler that never finished on an agent's code (CodeWarrior on a GameCube project, looping on one #pragma) kept ninja and the pipe open, so the timeout was never reached: the build held the project's build lock, every compile_and_diff after it waited behind it, and the agent's change was never undone. Agents' calls timed out one after another for hours with no compile. The output is now read on a thread beside the build, so the timeout counts while the build runs. At the timeout the build is stopped with everything it started (stop_process_tree: killing ninja alone leaves the compiler holding the pipe), and it fails with a sentence saying so; the compile path already undoes a change whose build fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Thanks, this fixes the important part: a build that never finishes no longer holds the build lock, and 1. On Linux and macOS the compiler survives the timeout. 2. The reader thread can outlive 3. The test doesn't cover "everything it started". Smaller, fine as follow-ups:
Same "read to EOF, then wait with no timeout" pattern elsewhere, not for this PR: |
Requested by Bluesfire
Type: bug fix
Summary
ForkProject.runread the build's output to the end, and only then calledproc.wait(timeout=...). A compilerthat never finishes keeps the pipe open, so the loop never ended and the timeout was never looked at. On a GameCube
game, an agent compiled a function with a
#pragma schedulingvalue that CodeWarrior 2.5 loops on (100% CPU, noend). That build held the project's build lock for 8½ hours: every
compile_and_diffafter it waited behind it, andthe agents' MCP calls timed out at 300 s one after another. That made 61 helper sessions with 0 compiles, and about
$7.50 of Claude usage plus 14M Codex tokens spent on nothing. The change was never undone either, so after a restart
the next compile of that file froze the app again, two minutes in.
compile_and_diffalready treats a build that fails as a BUILD ERROR and undoes the change, so the agent's codegoes and the lock is free again.
Evidence
Before (upstream
main): last night's file, built by a parent process that startsmwcceppc.exeas ninjadoes, with
timeout=20:run()had not returned after 88.8 s, when I stopped it from outside, and nothing saidwhy.
After:
Full suite on Windows: per-test outcomes identical to upstream
mainapart from the new test (735 passed, 230skipped, 2 failed; the same 2 fail on
main:test_when_a_usage_limit_resets_is_read_from_what_the_agent_saidand
test_the_stubs_visual_cpp_makes_are_found[7.1]).uv run ruff check src tests projects: passes.Merge Danger
Door: two-way
Blast Radius: every build
ForkProject.runstarts (a unit's compile, build_all, a project's setup)A build that used to finish late but still finish now fails at its timeout (600 s for a unit, 1800 s for
build_all, 3600 s by default). One that went past those used to hang without end, so that's the only case that
changes. A Claude Code helper still gives up on its own call at 300 s, but the next compile goes through once the
600 s are up, where before none ever did. On Linux
stop_process_treesends SIGTERM, which ninja passes on to thecommands it runs. A program a build leaves running after it ends (a compiler's helper server) can't hold
run()formore than 5 s.
🤖 Generated with Claude Code