Skip to content

session-flow/keep-going: a usage-limit message is trusted across an account switch #3915

Description

@kyle-sexton

Finding

/session-flow:keep-going treats a captured usage-limit message as authoritative about the current account. When the operator switches accounts between the message being emitted and the skill being run, the skill reports a limit that no longer applies and routes to its still-blocked branch, which tells the session to hand off and stop when it could have kept working.

Observed 2026-09-07 at roughly 03:52 EDT in session 71b98337-8fd3-464f-b293-38210ab39e9a, Claude Code v2.1.261, session-flow 0.34.24.

Subagents died with:

You've hit your weekly limit · resets Sep 8, 6pm (America/New_York)

Following the skill's "After a usage limit lifts" section I ran the bundled checker against that text, concluded the limit held for another ~38 hours, and told the operator no subagent could resume until Sep 8. The operator had since logged into a different account, whose live limits at that moment read 5h-limit: 7% (resets: 4h 48m | 8:50 AM) and 7d-limit: 14% (resets: 1d | Tue 8:00 PM). There was ample headroom and the conclusion was simply false.

Why the skill cannot currently catch this

The skill's reset-source paragraph states that the only in-session sources are the limit message text and the interactive /usage view, and instructs the reader to take the reset from the message and never invent a window. Nothing in that procedure establishes which account the message came from, so a message that predates an account switch is trusted unconditionally. The failure is silent and confident: the arithmetic is correct, the premise is stale.

This matters more than an ordinary stale read because of where the answer flows. The still-blocked branch composes with /session-flow:handoff and stops the session. So a stale message does not merely mislead, it ends a session that had budget to continue.

Two secondary findings from the same run

The checker's unparsable path did not fire on a date-bearing reset. scripts/check-usage-limit-reset.py printed unparsed: no reset clause found for resets Sep 8, 6pm (America/New_York) while the observed exit status was 0. The skill documents exit 2 as "no parseable reset clause; say so plainly and ask the operator rather than guessing", and exit 0 as "the reset has passed, treat the worker as resumable now". A caller reading only the exit code, which is what the skill instructs, would read an unparsed message as a lifted limit. The bare resets 3:45pm form the skill's own example uses parses; the Sep 8, 6pm form does not appear to.

The statusline does expose live limit state. It rendered both the 5-hour and 7-day percentages and their reset times. That contradicts the skill's claim that no surface beyond the message text and /usage exposes this in-session, and it is a candidate source for the freshness check below.

Suggested direction, not prescriptive

  • Establish account identity alongside the reset before trusting a captured message, and treat an identity that cannot be established as unknown rather than as unchanged.
  • Prefer a live reading over a captured message where one is available, and fall back to the message only when it is not.
  • Make the checker's unparsable path fire for every reset form the product actually emits, including the date-bearing one, so a caller reading the exit code cannot mistake unparsed for lifted.
  • Correct the reset-source paragraph if the statusline is in fact a usable in-session surface.

Acceptance criteria

  • A limit message captured under one account does not by itself drive a still-blocked verdict after an account switch.
  • The checker exits with its documented unparsable status for a reset clause it cannot parse, including the Sep 8, 6pm (America/New_York) form, and never exits 0 on an unparsed message.
  • The skill's statement of which in-session surfaces expose limit state matches what is actually available.
  • A test pins the date-bearing reset form against whichever exit status is chosen for it.

Out of scope

  • Changing what the skill does once a limit genuinely holds; the handoff-and-stop behaviour is correct when the premise is true.
  • Inventing a reset window from anything other than a source that actually reports one.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: mediumReal value, no hard deadline; normal backlog flow.work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions