feat: use interrupt() as graceful timeout alternative#353
Conversation
Replace single-tier AbortController.abort() timeout with a two-tier mechanism: soft timeout calls query.interrupt() for graceful stop, hard timeout calls controller.abort() as fallback after grace period. New config fields timeout_strategy and interrupt_grace_ms control behavior. Default: "interrupt_first" with 10s grace period. "abort_only" preserves legacy behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Claude finished @sjnims's task in 1m 23s —— View job Review: PR #353 — feat: use interrupt() as graceful timeout alternative
Verdict: ✅ Looks goodWell-structured feature that cleanly adds a two-tier timeout mechanism. The Quality Checks
What's Good
Suggestions
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
- Update JSDoc on deriveTerminationType to include "interrupted" priority - Update JSDoc on TerminationType to document "interrupted" variant - Extract addInterruptErrorIfNeeded helper to reduce duplication - Remove unused queryHolder field from TwoTierTimeout return interface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
Addressed all three suggestions in 37ef25f:
|
|
Claude finished @sjnims's task in 2m 23s —— View job Re-review: PR #353 — feat: use interrupt() as graceful timeout alternative
Verdict: ✅ Looks goodAll three suggestions from the previous review have been properly addressed in 37ef25f. Quality Checks
Fixes Verified
No new issues found. The code is clean and ready to merge. |
Description
Replace single-tier
AbortController.abort()timeout with a two-tier mechanism:timeout_ms: callsquery.interrupt()for graceful stoptimeout_ms + interrupt_grace_ms: callscontroller.abort()as fallbackNew config fields
timeout_strategyandinterrupt_grace_mscontrol behavior. Default:"interrupt_first"with 10s grace period."abort_only"preserves legacy behavior.Key design: A
QueryHolderpattern ({ query: Query | undefined }) allows timeout callbacks to lazily access the query object, which doesn't exist when timers start. If query isn't created when soft timeout fires, falls back to hard abort.Type of Change
Component(s) Affected
Pipeline Stages
src/stages/3-execution/)src/stages/4-evaluation/)Core Infrastructure
src/config/)src/types/)Other
tests/)CLAUDE.md,README.md)Motivation and Context
When a scenario times out, the current behavior is a hard
AbortController.abort()which loses any partial results the agent may have produced. Usingquery.interrupt()first gives the SDK a chance to gracefully wind down and return partial results before the hard abort fires as a safety net.Fixes #347
How Has This Been Tested?
Test Configuration:
Test Steps:
npm run build— clean compilationnpm run typecheck— type safety verifiednpm run lint— no lint errorsnpm run format:check— formatting cleannpm run knip— no dead exportsnpm test— 1605 tests passed, 5 skippedChecklist
General
TypeScript / Code Quality
npm run typecheck)_anytypes without justificationDocumentation
Linting
npm run lintand fixed all issuesnpm run format:checkTesting
npm testand all tests passStage-Specific Checks
Stage 3: Execution (click to expand)
Files Changed
src/types/transcript.ts"interrupted"toTranscriptErrorTypeandTerminationTypesrc/types/config.tsTimeoutStrategytype,timeout_strategy/interrupt_grace_mstoExecutionConfigsrc/types/index.tsTimeoutStrategysrc/config/schema.tsTimeoutStrategySchema, new fields toExecutionConfigSchemasrc/config/defaults.tstimeout_strategy: "interrupt_first",interrupt_grace_ms: 10000src/stages/3-execution/timeout-strategy.tscreateTimeout()helper withQueryHolderpatternsrc/stages/3-execution/transcript-builder.tscreateInterruptedError()helpersrc/stages/3-execution/agent-executor.tsExecutionContext,prepareExecutionContext, both execute functions,deriveTerminationTypesrc/stages/3-execution/session-batching.tsexecuteBatchandexecuteScenarioWithRetryto use two-tier timeoutsrc/stages/4-evaluation/metrics.tsinterrupted: 0to error counts recordtests/mocks/sdk-mock.tsshouldInterrupt,interruptRequestedflag, functionalinterrupt()tests/unit/stages/3-execution/timeout-strategy.test.tstests/unit/stages/3-execution/agent-executor.test.tsderiveTerminationTypetests for"interrupted"tests/unit/stages/3-execution/session-batching.test.tsReviewer Notes
Areas that need special attention:
QueryHolderpattern intimeout-strategy.ts— verify the lazy access approach is soundderiveTerminationType: stopReceived > timeout > interrupted > error > cleanKnown limitations or trade-offs:
interrupt()succeeds but thefor-awaitloop hangs, the hard abort fires after grace period as a safety netshouldInterruptmock simulates interrupt by yielding the result message and returning early, which is a simplification of real SDK behavior🤖 Generated with Claude Code