Commit d3f1432
fix(antigravity): poll for backgrounded work instead of grading it incomplete (#111)
* fix(antigravity): poll for backgrounded work instead of grading it incomplete
AntigravityAgent.communicate() used to drain the SDK's step stream once and
finalize the instant it went idle, so a task Gemini backgrounds and pauses on
(intending to check back later) got graded before the work finished. Now the
turn polls (sleep + re-drain) while an orphaned tool call is still ACTIVE,
bounded by _MAX_BACKGROUND_POLLS and the existing turn watchdog.
Hardened across three review rounds: the orphan signal allowlists ACTIVE
(not "not yet closed") so a tool stuck on WAITING_FOR_USER/CANCELED/UNKNOWN
is never polled forever; the fallback synthetic tool-call id (when the SDK's
call.id is falsy) is stable across a step's own ACTIVE->DONE re-emissions and
unique across trajectories; the poll loop exits promptly once the watchdog
has decided to fire; and _drain() retries past the SDK's real two-layer
generator re-entrancy window after a cooperative stop instead of crashing the
next turn.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: remove redundant asyncio re-import flagged by CodeQL
The module already imports asyncio at the top level; the local re-import
inside test_communicate_poll_budget_exhausted_finalizes_via_existing_timeout_path
was dead weight. The other CodeQL finding on this PR (an unreachable trailing
yield in a fake receive_steps() that raises CancelledError first) is a
deliberate, necessary idiom -- Python only classifies a function as an async
generator if its body contains a yield anywhere, reachable or not, and the
same pattern already exists twice, unflagged, on main.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix: derive the poll loop's exit bound from the turn's actual timeout
The poll loop's graceful-exit path (force-close a never-resolving orphan as
unresolved, finalize and grade normally) was bounded by a fixed cycle count
(120 x 5s = 600s) that was double experiments/default.yaml's own default
turn_timeout (300s). Since the pre-existing ThreadedWatchdog enforces timeout
by cancelling the whole turn, it always won that race under default settings,
making the graceful path dead code: a tool call spuriously left ACTIVE with no
real background job behind it (a real, observed case from the PR's own
validation run) went from "finalizes immediately, graded on whatever the agent
wrote" pre-fix to "burns the full 300s, then crashes as TurnTimeoutError with
zero criteria graded" post-fix -- a strict regression for that input class.
Fixes by deriving a poll_deadline from 0.8x the actual timeout passed to
communicate(), falling back to the cycle-based cap only when timeout is None.
0.8 is a fraction of the watchdog's own deadline, not an identical value, so
it doesn't reintroduce the race an earlier review round removed -- it's a
deliberately earlier internal deadline engineered to reliably win.
Caught independently by two PR reviewers (bai-uipath, uipreliga) on the same
line of arithmetic. Added a regression test proving a never-resolving orphan
under a realistic 300s timeout now finalizes gracefully instead of crashing.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>1 parent 02e5151 commit d3f1432
3 files changed
Lines changed: 1000 additions & 23 deletions
File tree
- .claude
- src/coder_eval/agents
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
314 | 314 | | |
315 | 315 | | |
316 | 316 | | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
0 commit comments