fix(#7240): detect caller deadlines via ctx.Err() helper - #7241
fix(#7240): detect caller deadlines via ctx.Err() helper#7241fullsend-ai-coder[bot] wants to merge 2 commits into
Conversation
errors.Is against context.DeadlineExceeded also matches net/http Client.Timeout (and mintclient.retryableError after Unwrap), so a post-script remint truncated by a transport timeout was reported as the remint context expiring (#7234). This is the third occurrence of the same pitfall (#6424, #6425). Add internal/ctxerr.IsDeadlineExceededOrCanceled, which reports true only when err is non-nil and the caller's own context is done. Migrate every errors.Is(err, context.DeadlineExceeded) site under internal/ to the helper. forge.IsTransient now takes ctx so nested timeouts stay retryable while genuine caller expiry does not. Note: pre-commit could not fetch remote hook repos (git HTTP 403). Hooks were run directly: gofmt, go vet, gitleaks on changed files, lychee, lint-docs-links, trailing-whitespace/EOF. fetch package TestPortRestriction fails in this sandbox (DNS blocked); the isTransientRequestError tests passed. Closes #7240
|
🤖 Finished Review · ✅ Success · Started 6:58 PM UTC · Completed 7:14 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $4.48 |
|
Risk Assessment: moderate (2/5) DetailsWeighted composite (0.50×Tier1=1.5 + 0.30×Tier2≈2.9 + 0.20×Tier3≈1.5 ≈ 1.95) rounds to 2 (moderate): Tier 1 signals are unchanged from the prior review (bot-authored, no protected/security/dependency paths, moderate size), and the only delta since the prior assessment — a small in-place fix to already-counted internal/harness/compose.go and compose_test.go addressing the prior HIGH finding — nudges Tier 2 churn/coupling slightly without moving the rounded composite, so the prior score of 2 is preserved per re-review anchoring. Previous runRisk Assessment: moderate (2/5) DetailsA bot-authored, well-tested bug fix (Tier1=1.5, 50%) consolidating a recurring context-deadline misclassification bug touches several high-churn, multi-author core files (Tier2=2.7, 30%) but maps closely to a well-scoped, unlabeled, uncontested linked issue with no rollback flag (Tier3=1.67, 20%), yielding a composite of ~1.9 rounded to 2 (moderate). |
ReviewFindingsLow
Previous runReviewFindingsHigh
Low
Next steps:
|
|
🤖 Finished Fix · ✅ Success · Started 7:16 PM UTC · Completed 7:25 PM UTC Commit: Runtime: claude · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $0.92 |
Migrating internal/harness/compose.go's isTransientFetchError to ctxerr.IsDeadlineExceededOrCanceled dropped the case this classifier depends on: a nested timeout (e.g. a git-fetch HTTP client's own Client.Timeout unwrapping to context.DeadlineExceeded) with a still-live caller context. That broke the existing TestFetchBaseSkill_StaleCacheContextDeadlineFallback test, which relies on a wrapped DeadlineExceeded under a live context.Background() still triggering the stale-cache fallback. Restore errors.Is(err, context.DeadlineExceeded/Canceled) alongside the caller-ctx-done check, so this classifier (unlike most ctxerr.IsDeadlineExceededOrCanceled call sites) treats caller expiry and nested timeouts the same way, since both are fine reasons to serve stale cache. Update the TestIsTransientFetchError case that encoded the now-reverted behavior. Addresses the HIGH finding on #7241.
🔧 Fix agent — iteration 1 (bot-triggered)Fixed the HIGH finding: isTransientFetchError in internal/harness/compose.go no longer requires the caller's own context to be done in order to treat a wrapped context.DeadlineExceeded/Canceled (e.g. from a nested git-fetch HTTP timeout) as transient, restoring the stale-cache fallback behavior that TestFetchBaseSkill_StaleCacheContextDeadlineFallback depends on. Updated the one test case that had encoded the broken behavior. The LOW finding about forge.IsTransient's widened signature required no code change per the reviewer's own conclusion, recorded as a disagreement/acknowledgment. Fixed (2):
Disagreed (1):
Tests: passed Decision points
Next steps:
|
|
🤖 Review · ❌ Terminated · Started 7:27 PM UTC · Ended 7:42 PM UTC Commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Superseded by updated review
|
🤖 Finished Review · ✅ Success · Started 7:27 PM UTC · Completed 7:42 PM UTC Commit: Runtime: pi · Model: sonnet → claude-sonnet-5 · Effort: high · Cost: $2.61 |
Summary
A post-script remint that failed on a net/http Client.Timeout was reported as "timed out after N" (caller-context truncation) instead of a generic mint failure.
errors.Is(err, context.DeadlineExceeded)matches those transport timeouts because they unwrap toDeadlineExceededfrom an internal context — the same pitfall documented in #6424 and hardened in one call site in #6425, then reintroduced in #7234.This PR is the completion of that series: one exported, tested helper that checks
ctx.Err(), and everyinternal/detection site routed through it.Related Issue
This addresses #7240. Completes the work started in #6424 and #6425.
Changes
internal/ctxerr.IsDeadlineExceededOrCanceled(ctx, err): true only whenerr != niland the caller's own context is done. A live context is never reported as expired, even whenerrunwraps toDeadlineExceededor implementsTimeout() bool.internal/cli/run.go),appsetup.ensureInstalled,harness.isTransientFetchError,forge.IsTransient,github.isTimeoutError/do(),fetch.isTransientRequestError,gcp.isRetryableTransportError.forge.IsTransientnow takesctxso nested HTTP timeouts stay retryable while genuine caller expiry does not.docs/contributing/go-code.mdso reviewers flag newerrors.Is(err, context.DeadlineExceeded)sites instead of requiring that (incorrect) guard.Testing
mintclient.retryableError, asserting false when the caller's context is still live.forge.IsTransientandisTransientFetchErrortables now distinguish live vs expired contexts.gofmt,go vet, targeted package tests, and secret scan passed.pre-commitcould not fetch remote hook repos in this sandbox (git HTTP 403); hooks were run directly.Checklist
fix, issue number as scope)Closes #7240
Post-script verification
agent/7240-ctxerr-deadline-helper)773149d0e4f04f7ee5d831acbcf3b083baf9aa2b..HEAD)