Skip to content

[Bug]: A thread that woke from snooze cannot be snoozed again to the same wake time #14298

Description

@vitalyiegorov

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Snooze a thread with Next week.
  2. Let its session fail after the snooze. In my case two Claude threads ran turns that failed at once with "Claude usage limit reached". The thread comes back as Failed, as intended: a failure newer than the snooze raises its hand.
  3. Snooze it again with Next week.

Expected behavior

The thread leaves the inbox until the new wake time.

Actual behavior

The command is accepted, but the thread stays in the inbox as Failed. Snoozing again with the same option does the same every time. I did it six times in 12 minutes on one thread. It looks like the snooze hangs or does nothing, with no toast.

Cause

A re-snooze to the same wake time is treated as a duplicate, and the original snoozedAt is kept:

  • server: apps/server/src/orchestration/decider.ts, thread.snooze, the existingSnoozedAt branch
  • client optimistic twin: packages/client-runtime/src/state/threadCommands.ts, snoozedAt: thread.snoozedUntil === input.snoozedUntil ? (thread.snoozedAt ?? now) : now

The raised-hand check in threadRaisedHandWhileSnoozed (packages/client-runtime/src/state/threadSettled.ts) compares session.updatedAt and latestTurn.completedAt against that stale snoozedAt. The failure is still newer, so the thread never classifies as snoozed again until the user picks a different wake time. Presets such as Tomorrow and Next week resolve to fixed clock times, so repeating the same option is the normal case.

Suggested fix

Remove the same-wake-time branch in both places, so an explicit snooze always stamps snoozedAt now. Re-snoozing means "I saw it, not now", so it should reset the raised-hand baseline. Real retries are already deduplicated by commandId receipts, and a double-click only moves the stamp by milliseconds. This is a net deletion. Update the existing decider.snoozed.test.ts case ("re-emits idempotently for a duplicate snooze to the same wake time") to assert a fresh snoozedAt. I can open the PR.

Impact

Minor bug or occasional failure

Version or commit

t3@0.0.43-nightly.20260929.2428 (main d2c9281). The code is unchanged on current main.

Environment

Linux host running t3 serve as a service, macOS desktop client connected remotely, Claude provider. Not specific to remote connections: the server accepted every command.

Logs or stack traces

# orchestration_events for one thread (UTC)
17:30:47.626  thread.snoozed      client    snoozedUntil 2026-10-05T07:00Z  snoozedAt 17:30:47.626
17:45:45.170  thread.session-set  provider  status error  "Claude usage limit reached…"
17:58:01.695  thread.session-set  provider  status error  "Claude usage limit reached…"   <- raises hand
17:58:19.209  thread.snoozed      client    snoozedUntil 2026-10-05T07:00Z  snoozedAt 17:30:47.626  <- stale
17:58:33.557  thread.snoozed      client    …same…
17:58:52.528  thread.snoozed      client    …same…
18:03:21.077  thread.snoozed      client    …same…
18:04:53.764  thread.snoozed      client    …same…
18:10:21.969  thread.snoozed      client    …same…
# all receipts: accepted; projection_threads.snoozed_at stays 17:30:47.626

A second thread shows the same pattern (snoozed 17:30:40, error at 18:05:45, re-snoozes at 18:08 and 18:10 all keep 17:30:40).

Screenshots, recordings, or supporting files

Both threads after six "Next week" re-snoozes between 17:58 and 18:10. Every command was accepted, and both rows are still in the inbox as Failed:

Sidebar: two Claude threads stopped by a usage limit stay in the inbox as Failed after repeated re-snoozes

Workaround

Snooze with a different option, such as Tomorrow or 3 hours. That stamps a fresh snoozedAt and the snooze holds.

Related

Activity

  1. changed the title [-][Bug]: Snoozing a woken thread again with the same option is accepted but does nothing[/-] [+][Bug]: A thread that woke from snooze cannot be snoozed again to the same wake time[/+] on Sep 29, 2026
  2. vitalyiegorov commented on Sep 29, 2026

    @vitalyiegorov
    ContributorAuthor

    PR opened: #14299. It removes the same-wake-time branch in the decider and its optimistic twin, so every snooze stamps a fresh snoozedAt. The existing duplicate-snooze test now asserts the fix and fails without it.

  3. juliusmarminge commented on Sep 29, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on main at d2c9281b81. A re-snooze to the same wake time keeps the original snoozedAt, so a failure or completion that is still newer than that stamp keeps the thread in the inbox.

    thread.snooze in apps/server/src/orchestration/decider.ts treats an unchanged snoozedUntil as a duplicate and re-emits the stored snoozedAt and updatedAt. The optimistic twin in packages/client-runtime/src/state/threadCommands.ts does the same (snoozedAt: thread.snoozedUntil === input.snoozedUntil ? (thread.snoozedAt ?? now) : now). The projector copies payload.snoozedAt through, so the row never moves. The reported events match that: six accepted thread.snoozed events, all still stamped 17:30:47.626.

    Raising a hand does not clear the snooze fields. threadRaisedHandWhileSnoozed only stops the thread from classifying as snoozed when a session error's updatedAt, or a completed turn's completedAt, is newer than snoozedAt. snoozedUntil stays set, so the next click of the same preset compares equal and the old stamp wins. The comment next to that check already says a snooze of an already-failed thread means "I saw it, not now". This branch makes that false whenever the wake time does not change.

    Calendar presets are stable. resolveSnoozePresets pins This evening, Tomorrow, and Next week to a local clock time, so repeating Next week is the same snoozedUntil (here 2026-10-05T07:00Z). In 1 hour and In 3 hours mint a new instant on every click, which is why a different option hides the thread.

    This is not a duplicate of #6368. That issue's first half, completed work waking a snoozed thread, is the current raised-hand policy and is untouched here. Its "re-snooze may not land in Snoozed" half is this bug, but #6368 has no cause and also asks for the wake policy to change. Closed #7179 only removed the completion comparison in threadRaisedHandWhileSnoozed and never merged; it did not touch this branch. #10545 is why a usage-limit stop reads as Failed. That is what raised the hand, not why the re-snooze was ignored.

    The same-wake branch is not the retry guard. OrchestrationEngine returns the existing receipt before the decider runs, so a replayed commandId never reaches this code. A double-click is a new command and only moves the stamp by milliseconds. The raised-hand checks are strict >, so that is enough.

    Always stamp snoozedAt and updatedAt from now, in the decider and the optimistic twin. The decider test "re-emits idempotently for a duplicate snooze to the same wake time" asserts the bug and has to flip. Open PR #14299 does that.

    Workaround until it ships: pick a different wake time.

  4. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 29, 2026
  5. vitalyiegorov commented on Oct 5, 2026

    @vitalyiegorov
    ContributorAuthor

    Still happens on V2: thread.snooze in Orchestrator.ts keeps the original snoozedAt for a re-snooze to the same wake time. Fix rebuilt on V2 main in #15881.

  6. added a commit that references this issue on Oct 6, 2026
    58c1440
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

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions