DEV Community

Ashwar Sadh
Ashwar Sadh

Posted on Fully Autonomous

Resuming work at a time you only know from an error message

Disclosure: I'm the developer of the tool described here, and an AI agent drafted this post from the project's source code.

When a long-running AI coding session stops on a usage limit, the only record of when it can continue is a sentence in an error, something like "resets 3pm (Asia/Kolkata)". If you have one session, you come back later and type "continue". If you have a dozen, they all stall overnight and you lose the hours between the reset and your next look.

I added auto-resume to Relaymote (MIT, Node.js), a phone remote for Claude Desktop's Code-tab sessions. This post walks through the pieces, because the same problem shows up anywhere a job is paused by a quota and the only schedule is human-readable text: CI minutes, API rate windows, scraping quotas.

1. Classify before you parse

Not every error deserves a scheduled resume. A quota stop has a known end; a transient rate limit or an "overloaded" reply does not. Relaymote keeps two small regexes and sorts errors into session-limit, rate-limited or "not ours":

function classify(error) {
  const e = String(error || '');
  if (!e) return null;
  if (LIMIT_RX.test(e)) return 'session-limit';
  if (RATE_RX.test(e)) return 'rate-limited';
  return null;
}
Enter fullscreen mode Exit fullscreen mode

A rate limit gets a flat retry (10 minutes by default). Only a session limit gets the reset-time treatment. Anything unrecognised is left alone, so a genuine bug never turns into a loop of "continue" messages.

2. Parse a wall-clock time in someone else's time zone

The reset text can be resets 3pm, resets at 15:00, or resets Oct 14, 9am (Europe/London). Three rules kept the parser honest:

  • Require a real time. A bare number like "resets 3" is rejected unless it has am/pm or minutes. A wrong guess here wakes a session hours early or late.
  • Use the zone in the text if it is a valid IANA name, else a configured default, else UTC. Intl.DateTimeFormat throws on an unknown zone, which makes a cheap validity check.
  • Roll forward, never backward. A time with no date that is already past means tomorrow. A dated time more than a day in the past means next year.

The trickiest part is converting "15:00 in Asia/Kolkata" to a UTC timestamp without a date library. JavaScript can format a UTC instant in any zone, but not the reverse. So guess, measure the error and correct:

function zonedToUtc(y, m, d, hh, mm, tz) {
  const want = Date.UTC(y, m - 1, d, hh, mm, 0);
  let guess = want;
  for (let i = 0; i < 3; i++) {
    const p = tzParts(guess, tz);            // what the wall clock in tz shows at `guess`
    const got = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute, p.second);
    const diff = want - got;
    if (!diff) break;
    guess += diff;
  }
  return guess;
}
Enter fullscreen mode Exit fullscreen mode

One correction is usually enough. The loop of three covers a daylight-saving change that lands between the guess and the answer. If no time can be parsed at all, Relaymote assumes a fixed window (5 hours) from when the error was recorded instead of giving up.

3. Printed reset times are conservative, so probe early

In practice a limit sometimes lifts before the printed time. Waiting for the exact minute leaves time on the table, but hammering the service is worse. So the resume plan has early probes on a halving schedule: the first fires 120 minutes before the reset, then 60, 30, 15 and so on, up to six probes, with at least a minute between any two.

const window = cfg.probeLeadMs / Math.pow(2, used); // 120, 60, 30, 15 ... minutes
const fireAt = c.dueAt - window;
Enter fullscreen mode Exit fullscreen mode

A probe that finds the session still limited is logged as "still limited" and does not count against the retry budget. A probe that gets through is logged with how many minutes early it succeeded, which over time tells you how conservative the printed times are.

4. Decide what to send by looking first

"Send 'continue'" is the wrong action surprisingly often. Before sending anything, the resume step looks at the session and picks one of:

  • already running or a message already queued: do nothing;
  • a half-typed draft in the box: send the draft, not a new message on top of it;
  • a visible Retry button: press Retry, which replays the turn that failed;
  • an empty composer: send a short "continue where you left off".

It also re-checks that the window is still on the same session immediately before acting, and returns nav-lost rather than typing into whatever the person navigated to.

5. Verify, cap and step aside

  • Verify. After sending, poll for up to 20 seconds until the session reports it is running or its error has cleared. "Sent" without "verified" is recorded as unverified, not success.
  • Cap. Three real attempts per stop, then it gives up and tells you. Stops older than 24 hours are ignored, because a day-old "continue" is more likely to surprise you than help.
  • Step aside. If the app itself is going to resume the session, Relaymote waits a two-minute grace period before acting, and records "self-resumed" if the app got there first. If you are typing at the PC, the resume is deferred rather than racing you for the keyboard.

Every decision is written to an audit log in plain sentences ("PROBE 3/6: still limited, 28 min before parsed reset; no attempt spent"). That log is what made the probe schedule and the caps tunable, because I could see what actually happened overnight.

Takeaways for any quota-bound job

  1. Classify first; only schedule work for errors that carry an end time.
  2. Parse times strictly, with an explicit zone, and roll forward only.
  3. Treat the printed reset as an upper bound and probe early on a decaying schedule that does not spend your retry budget.
  4. Look at the current state before acting; "resume" can mean send, retry or nothing.
  5. Verify, cap attempts, expire old stops, and log every decision in words.

The code is lib/resume.js in the repo; each knob above is a config value with a default. Questions and edge cases (time formats I have not seen yet especially) are welcome in the comments.

Relaymote is open source (MIT). Unofficial; not affiliated with or endorsed by Anthropic.

Top comments (0)