exec phase idles until run.timeout after the worker opened the PR, then reports 'Out of credits' from a stale rate-limit event

Open
#1,626 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
42/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Active
Tech stack
typescript
Domain
devtools

Research direction

Start by tracing the Claude Code driver’s phase-completion handling and the rate-limit path through isRateLimitFailureInfo, hasBillingMarkers, and billingHaltReason; the issue names no files or tests. Compare how a final worker result and a timeout are handled, and how rate-limit event status reaches errorMetadata. Done means completed work does not idle until timeout, and a timeout is not attributed to a non-rejected stale event.

Written by the indexing model from the issue text.

Description

bug

Summary

Two exec phases in one sequant run finished their work (PR opened, final assistant message posted) 18 and 24 minutes before run.timeout, then sat idle until the timeout ended them. The run table reported both as ✘ failed · Out of credits, although the subscription window was open and a follow-up run executed normally minutes later. State was left at exec: failed with open, green PRs and no exec marker on the issue, so --resume would re-run exec.

Two separate problems:

  1. Phase-complete detection waits on process exit. A worker that has pushed, opened the PR and posted its final message is done, but the phase stays open until the Claude Code process exits or run.timeout fires.
  2. A timeout is labelled with a stale rate-limit event. The driver retains any rate_limit_event with billing markers (overageDisabledReason: out_of_credits, five-hour window, live resetsAt) regardless of status (isRateLimitFailureInfo → hasBillingMarkers), and billingHaltReason then names it as the cause of a later, unrelated failure. The run log's errorContext carried {category: billing, errorType: BillingError, errorMetadata: {rateLimitType: five_hour, overageDisabledReason: out_of_credits, resetsAt: …}} on a phase whose real end was durationSeconds ≈ 1803. The event's status is not retained, so the log cannot show whether it was allowed_warning or rejected.

Environment

  • sequant 2.20.0, Claude Code driver, --no-tui -v, --resume --concurrency 2, phases spec,exec,qa, run.timeout 1800
  • macOS, Claude Max subscription (overage disabled)
  • Next.js/TypeScript repo, three small issues, exec on Sonnet

Timeline (UTC, from the run log and the worker transcripts)

Issue exec start PR opened + final message phase end idle
A 01:32:21 01:37:53 02:02:25 (1803.9 s, "Out of credits") 24 min
B 01:51:14 02:03:36 02:21:17 (1802.8 s, "Out of credits") 18 min
  • The third issue in the same run passed exec → qa normally (exec 2 min, qa 16 min).
  • A one-word claude -p probe answered at 02:20, and sequant run A B --phases qa ran and passed at 02:25–02:27, an hour before the recorded resetsAt.
  • Worker B had a 120 s tsc command that Claude Code moved to the background; its output file ends in [killed] stamped at the phase end, so that background task was alive at the kill. Worker A's background command had finished three minutes before its final message, so a lingering background task does not explain both.
  • Earlier timeouts in the same repo (four judgment issues on Opus, Oct 13–15) were a different shape: the worker was still active 1–5 min before the timeout and had no PR. Those were genuine time-outs.

Expected

  • A phase whose worker has opened the PR and posted a final message completes when the stream yields the final result, or after a short grace period, not at run.timeout.
  • A phase that ends by timeout is reported as Timeout after 1800s (as the Oct 13–15 runs were) unless the captured rate-limit event has status: rejected. Retaining status in errorMetadata would make this checkable after the fact.
  • On a timeout where the branch has a commit and an open PR, state should not be left as exec: failed without an exec marker; at minimum the summary should say the PR exists.

Repro (best effort)

Run an exec phase on a subscription account that is near its five-hour cap with overage disabled (so a rate_limit_event with overageDisabledReason: out_of_credits is emitted during the session), with a worker that starts a long tsc/jest command that Claude Code moves to the background, and let the worker finish and open the PR. Observe whether the phase ends at the final message or at run.timeout, and what reason the run table prints.

Happy to attach the two run logs (with repo-specific paths redacted) on request.

Dominant language
TypeScript
Stars
1
Forks
1
Avg merge
11h 3m
Merged PRs (30d)
84

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from sequant-io/sequant

All issues in sequant-io/sequant

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.