Claude Code 5-hour limit: inspect the graceful stop, save a checkpoint, and continue

A practical recovery workflow for Claude Code's five-hour limit: verify the repository, create a Git checkpoint, resume safely, and compare waiting, usage credits, and API routes.

Contents
Claude Code 5-hour limit: inspect the graceful stop, save a checkpoint, and continue

When Claude Code reaches its five-hour subscription limit in the middle of a coding task, do not treat the final response as proof that the task is complete. The new graceful-stop behavior is a best-effort buffer: Claude Code may use a small fixed allowance from weekly usage to move toward a safer stopping point, but it does not promise a clean Git state, a commit, passing tests, or a finished feature.

Use this order instead:

  1. Read the exact limit message and reset time.
  2. Inspect staged, unstaged, and untracked changes.
  3. Run the smallest useful validation.
  4. Create a recoverable Git checkpoint.
  5. Only then choose whether to wait, enable usage credits, or continue through an API route.

That sequence turns an uncertain interruption into a controlled handoff. It also prevents the most expensive mistake: paying to continue before you know what state the repository is in.

Graceful stop is a buffer, not a completed task

The Claude Developers announcement says Claude Code will try to find a graceful stopping point when the five-hour limit arrives mid-task, using a small fixed allowance drawn from weekly usage. The important words are try and what it can.

A single user report described a cleaner stop than the previous mid-edit cutoff. That is useful evidence that the behavior can help, but it is one account, not a reliability guarantee.

Keep three mechanisms separate:

MechanismWhat it protectsWhat it does not guarantee
Graceful stop at the usage limitGives the agent a little room to stop more coherentlyCompletion, commit, clean working tree, passing tests
Claude Code checkpointLets /rewind restore many direct file edits from the sessionBash-made changes, most subagent edits, external edits, full repository history
Git checkpointRecords a repository state you can inspect, branch, share, and restoreCorrectness unless you review and test it

The graceful stop can produce a better explanation or leave fewer half-written edits. Your Git inspection is still the acceptance step.

First, identify the limit that actually stopped you

Do not assume every interruption will disappear in five hours. Anthropic’s usage-limit documentation distinguishes usage limits from context-length limits, and usage may be shared across Claude surfaces covered by your account.

Read the message literally:

  • “Approaching 5-hour limit” means you still have a small window to stop starting new work and prepare a checkpoint.
  • “5-hour limit reached — resets …” gives the relevant reset time for that session window.
  • A weekly or model-specific limit may remain after the five-hour window resets.
  • Capacity or service errors are different from account usage exhaustion; paying for extra usage will not repair an outage.

If your current interactive client offers to wait and continue automatically after reset, keeping the session open may preserve the easiest continuation path. Verify the prompt shown by your installed version and mode: exiting the session can end that wait, and background, print, or API-key sessions can behave differently.

Inspect the repository before asking Claude to do more

Start from the repository root. These commands do not modify files:

git status --short --branch
git diff --stat
git diff
git diff --cached
git ls-files --others --exclude-standard

In Claude Code, /diff can also help review uncommitted changes, but Git remains the source of truth for the working tree.

Answer five questions before continuing:

  1. Which files changed?
  2. Which changes are staged, unstaged, or untracked?
  3. Is any file only partly edited—for example, a new call site without the matching type or test update?
  4. Did a generated file, dependency lockfile, migration, or configuration file change unexpectedly?
  5. What is the smallest command that can expose breakage now?

Then run a focused validation. Use the project’s real command, not a generic substitute:

# Examples only — choose the smallest relevant command for your repository
npm test -- path/to/affected.test.ts
pytest tests/test_affected.py -q
cargo test affected_module
git diff --check

A successful graceful stop is not “Claude printed a summary.” It is: you can name the changed files, the remaining work, the current test result, and the next safe action.

If the diff is hard to understand, stop. Do not ask the agent to “finish everything” on top of an unknown state. Make the checkpoint first.

Create a recoverable checkpoint: choose one of two methods

Method 1: make a reviewed local WIP commit

Use a WIP commit when the current state is coherent enough to record and your team permits temporary commits on a local branch.

git status --short --branch
git add -p
# Add only reviewed new files; git add -p does not automatically include all untracked files.
git add -- path/to/reviewed-new-file
git diff --cached --check
git diff --cached
# Run the smallest relevant test or lint command here.
git commit -m "WIP: checkpoint before Claude Code limit reset"
git rev-parse --short HEAD

Do not use git add -A blindly. Review .env files, credentials, private fixtures, generated artifacts, large binaries, and debug dumps before staging. A local WIP commit is a recovery point, not approval to push to a protected branch.

Record the commit ID and a short handoff:

Goal: finish token-expiry validation
Checkpoint: a1b2c3d
Changed: auth_service.py, tests/test_auth.py
Validation: targeted test still fails; expected 401, received 200
Next step: inspect the expiry comparison before changing callers

Method 2: save patches and reviewed untracked files outside the repository

Use this when you do not want to create a commit. The script resolves the real path of CLAUDE_CHECKPOINT_DIR first; if it is inside the repository—including through a symlink—it stops before creating any checkpoint files.

Run it in three stages: inspect the staged, unstaged, and untracked candidates and diffs without saving; select exact path numbers for each group, which explicitly excludes everything left unselected; then type SAVE REVIEWED before anything is written. Review the full diff or untracked file before selecting it, and exclude secrets, private material, and anything that should not be preserved. If only part of a file is safe to save, split the change or use the WIP commit method above instead of selecting the whole file.

The external method stops if it detects a tracked rename; use a reviewed WIP commit instead. If a selected unstaged path also has staged changes, review and save both versions. Before restoring, set checkpoint to the absolute directory printed by the save step. A wrong base, a dirty checkout, or any failed step stops restoration.

repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

# Step 1: inspect everything. These commands do not save files.
git status --short --branch
git diff --cached
git diff
git ls-files --others --exclude-standard

# Steps 2-3: choose reviewed paths explicitly, then save only that scope.
python3 /dev/fd/3 3<<'PY'
from datetime import datetime, timezone
from pathlib import Path
import json, os, shlex, subprocess, tarfile


def git(*args):
    return subprocess.run(["git", *args], check=True, stdout=subprocess.PIPE).stdout


def path_list(*args):
    return list(dict.fromkeys(Path(os.fsdecode(x)) for x in git(*args).split(b"\0") if x))


def choose(label, items):
    print(f"\n{label} candidates:")
    if not items:
        print("  (none)")
        return []
    for i, path in enumerate(items, 1):
        print(f"  {i:>3}  {shlex.quote(os.fspath(path))}")
    answer = input(
        "Select reviewed numbers to save "
        "(1,3-5; 'all'; blank saves none): "
    ).strip().lower()
    if not answer:
        return []
    if answer == "all":
        return items[:]
    chosen = set()
    try:
        for token in answer.split(","):
            bounds = [int(x) for x in token.strip().split("-", 1)]
            chosen.update(range(min(bounds), max(bounds) + 1))
    except ValueError as exc:
        raise SystemExit("Invalid selection; nothing was saved.") from exc
    if not chosen or min(chosen) < 1 or max(chosen) > len(items):
        raise SystemExit("Selection is outside the displayed range; nothing was saved.")
    return [path for i, path in enumerate(items, 1) if i in chosen]


def patch(cached, items):
    if not items:
        return b""
    args = ["diff"] + (["--cached"] if cached else [])
    return git(*args, "--binary", "--", *(os.fspath(path) for path in items))


repo = Path(git("rev-parse", "--show-toplevel").decode().rstrip("\n")).resolve()
root = Path(os.environ.get(
    "CLAUDE_CHECKPOINT_DIR", os.fspath(Path.home() / "claude-checkpoints")
)).expanduser().resolve(strict=False)
if root == repo or repo in root.parents:
    raise SystemExit("CLAUDE_CHECKPOINT_DIR must resolve outside the repository.")
root.mkdir(parents=True, exist_ok=True)
root = root.resolve()
if root == repo or repo in root.parents:
    raise SystemExit("CLAUDE_CHECKPOINT_DIR must resolve outside the repository.")

# A path-only selection cannot represent both sides of a detected rename.
if (git("diff", "--cached", "--find-renames", "--diff-filter=R", "--name-only", "-z")
        or git("diff", "--find-renames", "--diff-filter=R", "--name-only", "-z")):
    raise SystemExit("Renames require a reviewed WIP commit; nothing was saved.")

candidates = {
    "staged": path_list("diff", "--cached", "--name-only", "-z"),
    "unstaged": path_list("diff", "--name-only", "-z"),
    "untracked": path_list("ls-files", "--others", "--exclude-standard", "-z"),
}
if not any(candidates.values()):
    raise SystemExit("No staged, unstaged, or untracked changes were found.")
selected = {name: choose(name, items) for name, items in candidates.items()}
if not any(selected.values()):
    raise SystemExit("No reviewed changes were selected; nothing was saved.")
missing_baseline = set(selected["unstaged"]) & (
    set(candidates["staged"]) - set(selected["staged"])
)
if missing_baseline:
    raise SystemExit(
        "Selected unstaged paths also have staged changes. Select both reviewed "
        "versions or use a WIP commit; nothing was saved."
    )

not_saved = {
    name: [path for path in candidates[name] if path not in selected[name]]
    for name in candidates
}

print("\nSelected for saving:")
for name, items in selected.items():
    print(f"  {name}: " + (", ".join(shlex.quote(os.fspath(p)) for p in items) or "(none)"))
print("Excluded and NOT saved:")
for name, items in not_saved.items():
    print(f"  {name}: " + (", ".join(shlex.quote(os.fspath(p)) for p in items) or "(none)"))
if input("Type SAVE REVIEWED to save exactly this scope: ").strip() != "SAVE REVIEWED":
    raise SystemExit("Confirmation did not match; nothing was saved.")

current = {
    "staged": path_list("diff", "--cached", "--name-only", "-z"),
    "unstaged": path_list("diff", "--name-only", "-z"),
    "untracked": path_list("ls-files", "--others", "--exclude-standard", "-z"),
}
if current != candidates:
    raise SystemExit("The candidate list changed during review; rerun from step 1.")

stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
checkpoint = (root / f"{repo.name}-claude-{stamp}").resolve(strict=False)
if checkpoint == repo or repo in checkpoint.parents:
    raise SystemExit("Checkpoint path unexpectedly resolves inside the repository.")
checkpoint.mkdir(exist_ok=False)
(checkpoint / "base-commit.txt").write_bytes(git("rev-parse", "HEAD"))
(checkpoint / "status.txt").write_bytes(git("status", "--short", "--branch"))
(checkpoint / "staged.patch").write_bytes(patch(True, selected["staged"]))
(checkpoint / "unstaged.patch").write_bytes(patch(False, selected["unstaged"]))
(checkpoint / "untracked.list0").write_bytes(b"".join(
    os.fsencode(os.fspath(path)) + b"\0" for path in selected["untracked"]
))

manifest = {
    "selected": {name: [os.fspath(p) for p in items] for name, items in selected.items()},
    "not_saved": {name: [os.fspath(p) for p in items] for name, items in not_saved.items()},
}
(checkpoint / "selection.json").write_text(
    json.dumps(manifest, ensure_ascii=True, indent=2) + "\n", encoding="utf-8"
)
omitted = [
    f"{name}\t{json.dumps(os.fspath(path), ensure_ascii=True)}"
    for name, items in not_saved.items() for path in items
]
(checkpoint / "not-saved.txt").write_text(
    "\n".join(omitted) + ("\n" if omitted else "(none)\n"), encoding="utf-8"
)
if selected["untracked"]:
    with tarfile.open(checkpoint / "untracked.tar.gz", "w:gz", dereference=False) as archive:
        for path in selected["untracked"]:
            archive.add(repo / path, arcname=path.as_posix(), recursive=False)

partial = bool(omitted)
(checkpoint / "scope.txt").write_text(
    "PARTIAL: see not-saved.txt; this is not a complete recovery point.\n"
    if partial else
    "REVIEWED SCOPE: every listed candidate was saved; ignored files remain outside scope.\n",
    encoding="utf-8",
)
print(("PARTIAL checkpoint" if partial else "Reviewed checkpoint") + f" saved to {checkpoint}")
if partial:
    print("Some listed changes were not saved; read not-saved.txt before recovery.")
PY

selection.json records selected and excluded paths, not-saved.txt lists every omission, and scope.txt states the scope. If any staged, unstaged, or untracked candidate is excluded, both the terminal and scope.txt mark the result PARTIAL; it is not a complete recovery point and restores only the selected scope. Ignored files remain out of scope, and the checkpoint does not prove that the code works.

Before restoring, read scope.txt and not-saved.txt. Restore only the saved scope into a clean checkout at the recorded base commit:

(
    set -eu
    checkpoint="/absolute/path/printed-by-the-save-step" # Replace with the actual checkpoint directory.
    test -d "$checkpoint"
    repo_root="$(git rev-parse --show-toplevel)"
    cd "$repo_root"
    for required in base-commit.txt scope.txt not-saved.txt staged.patch unstaged.patch untracked.list0; do
        test -f "$checkpoint/$required"
    done
    cat "$checkpoint/scope.txt" "$checkpoint/not-saved.txt"
    test "$(git rev-parse HEAD)" = "$(cat "$checkpoint/base-commit.txt")"
    test -z "$(git status --porcelain --untracked-files=all)"
    if [ -s "$checkpoint/staged.patch" ]; then
        git apply --index "$checkpoint/staged.patch"
    fi
    if [ -s "$checkpoint/unstaged.patch" ]; then
        git apply "$checkpoint/unstaged.patch"
    fi
    if [ -s "$checkpoint/untracked.list0" ]; then
        tar -xzkf "$checkpoint/untracked.tar.gz"
    fi
    git status --short
)

If the current repository is no longer at that base commit, do not force the patches onto it. Create a clean branch or worktree at the recorded commit, restore there, and merge deliberately.

Use Claude Code checkpoints, but not instead of Git

Claude Code’s checkpointing documentation says direct file edits are tracked through the session and can be reviewed or restored with /rewind. The documentation describes retaining the most recent 100 checkpoints in a session and keeping them with the conversation so they can remain available after a resume.

That is useful for undoing a bad agent turn. It is not a repository backup. The documented limitations include files changed through Bash commands, most subagent edits, concurrent external edits, and some symlink or hardlink cases. Git also gives you a durable commit ID, branch semantics, reviewable patches, and a recovery path independent of the Claude session.

Use both layers:

  • /rewind for a quick session-level rollback.
  • Git commit or external patch bundle for a durable repository checkpoint.

To resume the conversation, the official CLI reference documents:

claude -c
claude -r "<session>"

After resuming, inspect git status again before authorizing more edits. A resumed conversation does not prove that the filesystem still matches the summary.

Decide whether to wait, use usage credits, or switch to an API

Make the choice only after the repository is recoverable.

SituationBest first choiceWhy
Reset is near, checkpoint is safe, deadline allows a pauseWaitNo additional metered charge or authentication change
You need the same Claude interface and conversation, and accept metered spendUsage creditsEligible paid accounts can continue in Claude and Claude Code after included usage is exhausted
You need a separate key, provider balance, or project budgetAPI routeBilling and authentication can be separated from the subscription
Limits interrupt normal work repeatedly over several daysReview plan/workflowOne interruption is weak evidence for a permanent upgrade

Wait when the interruption is cheaper than changing routes

Waiting is usually best when the reset time is close and your checkpoint is complete. Leave a handoff note, record the exact reset time, and keep the session open only if the current client explicitly offers waiting and automatic continuation.

When you return, run:

git status --short --branch
git diff --stat

Then restate the next step. Do not simply type “continue”; that invites the agent to infer missing state.

Use usage credits when continuity matters most

Anthropic’s usage-credits documentation says eligible Pro and Max users can enable extra pay-as-you-go usage in Settings > Usage, add funds, and set spending controls. The charge is separate from the subscription, and the normal five-hour windows still reset.

Before enabling it:

  • set a monthly cap and alerts;
  • decide whether auto-reload is acceptable;
  • confirm the interface says it is continuing with usage credits;
  • stop once the urgent checkpoint or acceptance test is complete.

Usage credits are convenient because the interface and conversation can remain the same. They are not “free overflow” from the subscription.

Use an API route when you need separate authentication or budget

An API route is a separate billing and authentication path. It does not automatically inherit your Claude subscription allowance, and changing only one environment variable may not override stored subscription authentication. In Claude Code, check /status and verify the active provider or gateway before sending a costly request. Then confirm the request in the provider’s usage log.

BetterToken is an Anthropic-compatible, pay-as-you-go API route—not an extension of a Claude subscription. It can be relevant when you want a separate key and balance. The safe checkpoint and context handoff matter regardless of API provider.

API list prices: compare the same model, but do not call it the task cost

The following standard text-token prices were checked on 2026-10-01. All figures are USD per one million tokens. Anthropic figures come from its official API pricing; BetterToken figures come from its pricing page.

Exact model IDAnthropic inputAnthropic outputBetterToken inputBetterToken output
claude-opus-5-5$4.00$20.00$2.72$13.60
claude-sonnet-5$2.00$10.00$1.36$6.80
claude-haiku-4-5-20251001$1.00$5.00$0.68$3.40

These numbers answer only one question: the listed standard input/output token price for the same model ID on that date. They do not make subscription quota, Anthropic usage credits, and API billing directly equivalent:

  • subscription usage is an included allowance governed by plan limits;
  • usage credits are metered continuation inside eligible Claude interfaces;
  • API usage is billed to a separate API/provider account by tokens and applicable features.

Actual task cost depends on input and output tokens, cache reads/writes, long-context or fast-mode rules, retries, tool calls, and account-specific terms. A lower list price does not prove that one route will cost less for your particular repository.

Resume with a state handoff, not “continue”

Whether you wait or change billing paths, give the next session a compact, verifiable handoff:

Goal: what must be true when the task is done
Base: branch and commit/checkpoint ID
Changed files: staged, unstaged, and untracked
Validation run: exact commands and results
Known failure: exact error or failing assertion
Do not touch: sensitive or unrelated files
Next action: one small, testable step

Then ask Claude Code to inspect the current diff and repeat the intended next action before editing. This catches a stale summary, a changed branch, or an incomplete graceful stop before it compounds.

Frequently asked questions

Does graceful stop automatically commit my work?

No. The official statement promises an attempt to find a safer stopping point, not a Git commit. Inspect and checkpoint the repository yourself.

Does graceful stop guarantee that the task is finished?

No. It may stop after explaining remaining work or after making only part of the requested edits. Tests and acceptance criteria still decide whether the task is complete.

Does the wrap-up cost extra money?

The announcement says the small fixed wrap-up allowance is drawn from weekly usage. Usage credits and API calls are separate metered routes. Check the message and billing mode shown by your account.

Can /rewind replace Git?

No. It is helpful for session-level direct edits, but documented exclusions mean it cannot represent every repository change. Keep a Git checkpoint for durable recovery.

What if the message names a weekly limit?

A five-hour reset may not clear a weekly limit. Save the checkpoint, follow the reset time and limit type displayed by the client, and choose extra usage only after confirming which counter is blocking you.

Ready to optimize your LLM workflow?

Join thousands of developers building faster, smarter, and more cost-effective AI applications with BetterToken.

Get Started for Free