Skip to content

refactor(server): settled state is now server-authored, ending client drift - #5462

Open
t3dotgg wants to merge 8 commits into
mainfrom
t3code/server-side-settled-logic
Open

refactor(server): settled state is now server-authored, ending client drift#5462
t3dotgg wants to merge 8 commits into
mainfrom
t3code/server-side-settled-logic

Conversation

@t3dotgg

@t3dotgg t3dotgg commented Aug 6, 2026

Copy link
Copy Markdown
Member

"Settled" was split across the stack: the server stored a user override while every client re-derived the actual classification from an inactivity window, per-row PR state, and clock heuristics. The copies had drifted — mobile hardcoded the 3-day window web made configurable, sorted the settled shelf by a different key, and inverted the capability-gate default — so the same thread could be settled on one device and active on another.

Now the server is the single author of settled state and clients just read settledOverride:

  • A new ThreadAutoSettleReactor sweeps once a minute and dispatches the existing thread.settle command for threads that qualify: quiet past the inactivity window, or on a merged/closed PR. All existing decider invariants and the activity-driven auto-unsettle apply unchanged, so a raced sweep can never hide live work.
  • The auto-settle window moved from per-device client settings (localStorage) to ServerSettings.threadAutoSettleAfterDays — one value per environment, same shelf on every device.
  • An open PR still blocks inactivity settling. The sweep reads cached VCS status, and verifies cold checkouts with a cooldown-limited, background-policy-gated live lookup so it never stampedes the forge.
  • Auto-settles backdate settledAt to the thread's last activity, and both platforms now sort the settled shelf by settledAt — fixing the ordering drift.
  • Snooze wakes count as activity, so a woken thread gets a fresh window instead of settling the moment it wakes.
  • Deleted from clients: the whole effectiveSettled derivation (window/PR/clock inputs, the "serverAdjudicated" clock-skew hack), the per-row PR-state lift-up machinery on web and mobile, web's useNowMinute hook, and mobile's hardcoded window. effectiveSettled is now a plain override read with a blocked-work guard.

Old servers never emit the override, so their threads simply stay active — same graceful degradation as before, minus a capability check per row.

Built by Claude Fable 5 via Claude Code.


Note

High Risk
Changes core thread lifecycle and list behavior across web, mobile, and orchestration; incorrect sweep or PR verification could hide active work or settle threads users still care about.

Overview
Settled threads are now decided on the server, not re-derived on each client from inactivity, PR state, and ticking clocks. Clients read settledOverride (with small guards for pending work and messages newer than settledAt).

A new ThreadAutoSettleReactor runs about once a minute: it evaluates threads with resolveAutoSettleVerdict / autoSettle.ts (inactivity window, merged/closed PR, open-PR blocks, snooze/pin/session guards) and dispatches thread.settle. PR checks use VcsStatusBroadcaster.peekStatus plus cooldown-limited live refreshStatus when background policy allows. threadAutoSettleAfterDays moves from client settings to ServerSettings; Beta UI writes server settings and is gated on threadAutoSettle.

settledAt is derived from last activity on settle (not command time). latestUserMessageAt is projected for decider/sweep logic. Web/mobile drop PR lift-up, useNowMinute, and client-side effectiveSettled window/PR logic; list v2 partitions use effectiveSettled(thread) and sort settled rows by settledAt.

Reviewed by Cursor Bugbot for commit b132fb0. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Move thread auto-settle from client-side inference to server-authored settled state

  • Introduces ThreadAutoSettleReactor on the server that periodically sweeps unsettled threads and dispatches thread.settle based on inactivity and PR state, replacing client-side clock/PR-state-based settlement logic.
  • The thread.settle decider now derives settledAt from the thread's last recorded activity (via threadLastActivityAt) rather than command time, preventing auto-settled threads from floating to the top of the settled shelf.
  • effectiveSettled in client-runtime is simplified to reflect only server-authored settledOverride, dropping autoSettleAfterDays, changeRequestState, and wall-clock inputs; a guard keeps a thread active if latestUserMessageAt is newer than settledAt.
  • Client UI (sidebar, mobile home, chat header, action menu) removes per-row PR state tracking, minute-quantized clocks, and settlement environment gating; all partition logic now calls effectiveSettled(thread) with no options.
  • threadAutoSettleAfterDays moves from client settings to server settings (default 3, null to disable), exposed in ServerSettings and gated in the UI behind a new threadAutoSettle server capability flag.
  • Risk: settled shelf ordering now uses settledAt (falling back to updatedAt) rather than latestUserMessageAt; threads settled before this deploy will have settledAt equal to command time rather than last activity.

Macroscope summarized b132fb0.

Summary by CodeRabbit

  • New Features

    • Added server-wide automatic thread settlement for inactive threads.
    • Added settings to enable, disable, and configure automatic settlement from 1–90 days.
    • Added support for environments to indicate automatic settlement availability.
  • Improvements

    • Thread settlement status is now consistently determined by server state across web and mobile.
    • Settled threads are sorted using their settlement time, with last update time as a fallback.
    • Snooze behavior and settlement safeguards remain supported.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly identifies the main change: settled state is now authored by the server to prevent client drift.
Description check ✅ Passed The description clearly explains the changes, rationale, server behavior, client updates, settings migration, and risks, but omits the template headings and checklist.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 6, 2026

@macroscopeapp macroscopeapp Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two Effect service convention violations in the new ThreadAutoSettleReactor service. The rest of apps/server/src already follows the canonical single-module make + layer shape (e.g. vcs/VcsStatusBroadcaster.ts, background/BackgroundPolicy.ts), so the new service is the outlier here. Everything else in the diff (namespace subpath imports, dependency acquisition via yield* Foo, pure-config options, test-only Layer.succeed/Layer.mock seams, VcsStatusBroadcaster.peekStatus addition, contracts/settings moves) looks consistent with the conventions.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/orchestration/Services/ThreadAutoSettleReactor.ts Outdated
Comment thread apps/server/src/orchestration/Layers/ThreadAutoSettleReactor.ts Outdated
Comment thread apps/server/src/orchestration/ThreadAutoSettleReactor.ts
Comment thread apps/server/src/orchestration/decider.ts Outdated
Comment thread apps/server/src/orchestration/ThreadAutoSettleReactor.ts
Comment thread apps/web/src/components/settings/BetaSettingsPanel.tsx
Comment thread packages/client-runtime/src/state/threadSettled.ts
Comment thread apps/server/src/orchestration/autoSettle.ts Outdated
Comment thread apps/web/src/components/settings/BetaSettingsPanel.tsx
@macroscopeapp

macroscopeapp Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

2 blocking correctness issues found. This PR introduces a new server-side auto-settle reactor and moves settled state derivation from clients to the server - a significant architectural and runtime behavior change. Additionally, there are unresolved review comments including a high-severity concern about cache coherence that could incorrectly settle active threads after branch switches.

You can customize Macroscope's approvability policy. Learn more.

@t3dotgg
t3dotgg force-pushed the t3code/server-side-settled-logic branch from bfa8139 to c2b77f2 Compare August 6, 2026 22:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/server/src/orchestration/Layers/ThreadAutoSettleReactor.ts`:
- Around line 169-185: The inactivity-candidate flow around
resolveAutoSettleVerdict must treat a cached "open" result from
peekChangeRequestState as "unknown" so it reaches the existing verification
path. Preserve the cooldown, background-policy gate, and verifyBudget checks,
then use verifyChangeRequestState to refresh and settle when the PR is merged or
closed. Add a focused test covering peekStatus returning open and refreshStatus
returning merged or closed.

In `@apps/server/src/orchestration/Services/ThreadAutoSettleReactor.ts`:
- Around line 19-35: Move ThreadAutoSettleReactor and its layer implementation
into the canonical orchestration/ThreadAutoSettleReactor module, inline
ThreadAutoSettleReactorShape in Context.Service, and export the service type,
make, and layer members there. Update all consumers to import
ThreadAutoSettleReactor from the canonical module instead of the Services/ or
Layers/ modules, removing the obsolete split definitions.

In `@apps/web/src/components/settings/BetaSettingsPanel.tsx`:
- Around line 112-114: Update the AutoSettleDaysInput usage in BetaSettingsPanel
so updateServerSettings is not called for every valid keystroke; commit the
fully validated draft threshold only on blur or Enter, preserving the existing
threadAutoSettleAfterDays setting update once editing completes.

In `@packages/contracts/src/orchestration.ts`:
- Around line 596-600: Keep auto-settle backdating server-only: in
packages/contracts/src/orchestration.ts:596-600, remove settledAt from the
client-callable thread.settle contract or provide a separate server-only
auto-settle command; in apps/server/src/orchestration/decider.ts:500-502, derive
the timestamp exclusively from trusted server projection data; in
apps/server/src/orchestration/decider.settled.test.ts:111-130, update coverage
to exercise the trusted server path without accepting a caller-provided
timestamp.

In `@packages/contracts/src/settings.ts`:
- Around line 536-541: The default for threadAutoSettleAfterDays must preserve
clients that previously persisted sidebarAutoSettleAfterDays: null instead of
enabling auto-settlement with 3 days. Add a one-time migration that carries the
explicit null forward, or change the server default to a disabled-safe value,
and add coverage verifying the persisted null case remains disabled after
decoding.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 732a9a94-b034-4f54-a494-b6616cb226ed

📥 Commits

Reviewing files that changed from the base of the PR and between 7251f1a and bfa8139.

📒 Files selected for processing (33)
  • apps/desktop/src/settings/DesktopClientSettings.test.ts
  • apps/mobile/src/features/home/HomeScreen.tsx
  • apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx
  • apps/mobile/src/features/threads/thread-list-v2-items.tsx
  • apps/mobile/src/features/threads/threadListV2.test.ts
  • apps/mobile/src/features/threads/threadListV2.ts
  • apps/server/integration/OrchestrationEngineHarness.integration.ts
  • apps/server/src/environment/ServerEnvironment.ts
  • apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
  • apps/server/src/orchestration/Layers/OrchestrationReactor.test.ts
  • apps/server/src/orchestration/Layers/OrchestrationReactor.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
  • apps/server/src/orchestration/Layers/ThreadAutoSettleReactor.test.ts
  • apps/server/src/orchestration/Layers/ThreadAutoSettleReactor.ts
  • apps/server/src/orchestration/Services/ThreadAutoSettleReactor.ts
  • apps/server/src/orchestration/autoSettle.test.ts
  • apps/server/src/orchestration/autoSettle.ts
  • apps/server/src/orchestration/decider.settled.test.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/server.ts
  • apps/server/src/vcs/VcsStatusBroadcaster.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/Sidebar.logic.test.ts
  • apps/web/src/components/Sidebar.logic.ts
  • apps/web/src/components/SidebarV2.tsx
  • apps/web/src/components/settings/BetaSettingsPanel.tsx
  • apps/web/src/hooks/useNowMinute.ts
  • packages/client-runtime/src/state/threadSettled.test.ts
  • packages/client-runtime/src/state/threadSettled.ts
  • packages/contracts/src/environment.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/settings.test.ts
  • packages/contracts/src/settings.ts
💤 Files with no reviewable changes (3)
  • apps/desktop/src/settings/DesktopClientSettings.test.ts
  • apps/mobile/src/features/threads/thread-list-v2-items.tsx
  • apps/web/src/hooks/useNowMinute.ts

Comment thread apps/server/src/orchestration/Layers/ThreadAutoSettleReactor.ts Outdated
Comment thread apps/server/src/orchestration/Services/ThreadAutoSettleReactor.ts Outdated
Comment thread apps/web/src/components/settings/BetaSettingsPanel.tsx
Comment thread packages/contracts/src/orchestration.ts Outdated
Comment thread packages/contracts/src/settings.ts
// Days of inactivity before the server auto-settles a thread; null disables
// auto-settle. Server-side (not a client setting) so every client sees the
// same settled shelf — the server derives settled state, clients only read it.
threadAutoSettleAfterDays: Schema.NullOr(ThreadAutoSettleAfterDays).pipe(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium src/settings.ts:539

threadAutoSettleAfterDays defaults to 3 days for every existing server, but the auto-settle interval was previously a client setting. An existing user who disabled auto-settle (persisted null) or set a custom interval will silently revert to three days after upgrading, and the server can auto-settle threads contrary to their saved preference. A migration that reads the former client-side value and seeds the new server-side field is needed before applying this default.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @packages/contracts/src/settings.ts around line 539:

`threadAutoSettleAfterDays` defaults to `3` days for every existing server, but the auto-settle interval was previously a client setting. An existing user who disabled auto-settle (persisted `null`) or set a custom interval will silently revert to three days after upgrading, and the server can auto-settle threads contrary to their saved preference. A migration that reads the former client-side value and seeds the new server-side field is needed before applying this default.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low

[settledShelfExpanded, snoozedShelfExpanded, threadListV2Layout, v2PendingTasks],

threadListV2Items passes ${nowMinute}:00.000Z as snoozeLabelNow to buildThreadListV2ListItems, which computes each snoozed row's snoozeWakeLabelText (the wake countdown). The useMemo dependency array omits nowMinute, and after this PR nowMinute was also removed from the threadListV2Layout dependencies — so a minute tick no longer invalidates either memo. Expanded snoozed rows keep a stale snoozeWakeLabelText until some other list state changes, so the countdown stops updating each minute. Add nowMinute back to the threadListV2Items dependency array.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/mobile/src/features/home/HomeScreen.tsx around line 666:

`threadListV2Items` passes `${nowMinute}:00.000Z` as `snoozeLabelNow` to `buildThreadListV2ListItems`, which computes each snoozed row's `snoozeWakeLabelText` (the wake countdown). The `useMemo` dependency array omits `nowMinute`, and after this PR `nowMinute` was also removed from the `threadListV2Layout` dependencies — so a minute tick no longer invalidates either memo. Expanded snoozed rows keep a stale `snoozeWakeLabelText` until some other list state changes, so the countdown stops updating each minute. Add `nowMinute` back to the `threadListV2Items` dependency array.

Comment thread apps/server/src/orchestration/decider.ts
Comment thread apps/server/src/orchestration/decider.ts
Comment thread apps/server/src/orchestration/decider.ts
@github-actions github-actions Bot added size:XXL 1,000+ changed lines (additions + deletions). and removed size:XL 500-999 changed lines (additions + deletions). labels Aug 6, 2026
Comment thread apps/server/src/orchestration/decider.ts
Comment thread apps/server/src/orchestration/projector.ts
Comment thread apps/server/src/orchestration/projector.ts
@t3dotgg
t3dotgg force-pushed the t3code/server-side-settled-logic branch from 12b1dc6 to 671e346 Compare August 7, 2026 06:20
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

Provider Metric Main baseline This PR Impact PR ceiling
Codex Total thread wire 11.3 KiB 11.3 KiB +12 B (+0.1%) 15.1 KiB
Codex Thread snapshot wire 5.5 KiB 5.5 KiB +3 B (+0.1%) 7.3 KiB
Codex Live turn WebSocket wire 5.9 KiB 5.9 KiB +9 B (+0.1%) 7.8 KiB
Codex Live turn WebSocket decoded 49.7 KiB 49.7 KiB 0 B (0.0%) 66.4 KiB
Codex Live turn messages 16 16 0 (0.0%) 21
Claude Total thread wire 11.3 KiB 11.3 KiB +6 B (+0.1%) 15.1 KiB
Claude Thread snapshot wire 5.5 KiB 5.5 KiB +10 B (+0.2%) 7.3 KiB
Claude Live turn WebSocket wire 5.9 KiB 5.9 KiB −4 B (−0.1%) 7.8 KiB
Claude Live turn WebSocket decoded 50.6 KiB 50.6 KiB 0 B (0.0%) 66.4 KiB
Claude Live turn messages 16 16 0 (0.0%) 21

Baseline: bfc69e4 · PR result: b132fb0 · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 94.6 KiB
  • Claude decoded thread snapshot: 95.4 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Comment thread apps/server/src/orchestration/ThreadAutoSettleReactor.ts
Comment thread apps/server/src/orchestration/ThreadAutoSettleReactor.ts
@t3dotgg
t3dotgg force-pushed the t3code/server-side-settled-logic branch from 671e346 to 407c63e Compare August 7, 2026 08:40
Comment thread apps/server/src/orchestration/ThreadAutoSettleReactor.ts Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 407c63e. Configure here.

Comment thread apps/server/src/orchestration/autoSettle.ts
@t3dotgg
t3dotgg force-pushed the t3code/server-side-settled-logic branch 2 times, most recently from 09f046c to 5199aca Compare August 7, 2026 10:16
return yield* updateCachedStatus(cwd, local, remote);
});

const peekStatus: VcsStatusBroadcaster["Service"]["peekStatus"] = Effect.fn(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High vcs/VcsStatusBroadcaster.ts:346

peekStatus merges independently cached local and remote halves without verifying they describe the same checkout. When refreshLocalStatusCore updates only cached.local after a branch switch, the stale cached.remote from the previous branch remains in the cache. mergeGitStatusParts pairs the new branch name from local with the old branch's PR data from remote, so the auto-settle reactor can see a new branch alongside a merged/closed PR from the prior branch and incorrectly settle an active thread. Consider invalidating the remote half when the local checkout identity changes, or adding a coherence check before merging in peekStatus.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/vcs/VcsStatusBroadcaster.ts around line 346:

`peekStatus` merges independently cached local and remote halves without verifying they describe the same checkout. When `refreshLocalStatusCore` updates only `cached.local` after a branch switch, the stale `cached.remote` from the previous branch remains in the cache. `mergeGitStatusParts` pairs the new branch name from local with the old branch's PR data from remote, so the auto-settle reactor can see a new branch alongside a merged/closed PR from the prior branch and incorrectly settle an active thread. Consider invalidating the remote half when the local checkout identity changes, or adding a coherence check before merging in `peekStatus`.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 72f1b2f at the consumer: peekChangeRequestState now requires the cached PR's headRef to equal the thread's branch, so a stale local/remote pairing after a branch switch (new refName + previous branch's PR) maps to "unknown" and live-verifies instead of settling. I kept the fix in the sweep rather than changing peekStatus/cache invalidation because the streaming path already tolerates the transient mismatch (rows re-render when the remote half refreshes) and the sweep is the only consumer that acts irreversibly on the merged view.

t3dotgg and others added 5 commits August 7, 2026 03:40
Settled classification used to be re-derived per client (inactivity window,
PR state, clock hacks), with real drift between web and mobile. The server
is now the single author of settled state: a ThreadAutoSettleReactor sweep
dispatches thread.settle for quiet threads and merged/closed PRs, the
auto-settle window moved to ServerSettings, and clients just read
settledOverride.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- settledAt is now derived in the decider from the thread's last recorded
  activity; the thread.settle command no longer accepts a caller-supplied
  timestamp, so shelf ordering cannot be forged.
- A cached "open" PR no longer blocks auto-settle forever: the sweep maps it
  to "open-cached", which triggers the cooldown-limited live verification the
  unknown state already used. Only a live-confirmed open PR holds a quiet
  thread active.
- The per-sweep verification budget is only consumed by lookups that actually
  ran; cooldown-suppressed calls no longer starve other checkouts.
- effectiveSettled keeps a stale "settled" override from hiding a thread with
  a user message newer than settledAt (queued-turn reactivation window),
  using shell-only comparisons.
- The auto-settle settings UI is gated on the threadAutoSettle capability and
  the days input commits on blur/Enter instead of per keystroke — transient
  values now drive a real server sweep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The command read model boots threads with an empty messages array, so the
  decider now reads the projected latestUserMessageAt stamp (newly carried on
  OrchestrationThread and maintained by the projector) alongside in-memory
  messages. Without it, a server restart erased user-message activity from
  both the queued-turn invariant and the settledAt stamp — an understated
  settledAt would then trip the client's reactivation guard and strand
  server-settled threads in the active list.
- settledAt candidates are clamped to the command time so a client-supplied
  future message timestamp cannot forge shelf position.
- An elapsed snooze wake now counts as activity for the settledAt stamp,
  matching the sweep's inactivity accounting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct conventions

Single canonical module with the interface inline in Context.Service (no
standalone *Shape type) and make/layer exports instead of the *Live suffix,
per the Effect service conventions check. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ts and bad data

- thread.reverted now recomputes the in-memory latestUserMessageAt from the
  retained messages, matching the SQL pipeline: a revert that removes the
  newest user message must not block settle/snooze on a message that no
  longer exists or backdate settledAt to reverted-away work.
- A malformed user-message createdAt no longer poisons the decider's
  newest-message reduction (Math.max with NaN), which would have silently
  disabled the queued-turn settle guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
t3dotgg and others added 3 commits August 7, 2026 03:40
… settings reads

- A cached status with no PR no longer settles a quiet thread on the
  inactivity path: the cache may predate the PR being opened, so it now maps
  to "unknown" and requires the same live verification as a cached "open".
  Only a live lookup (or a branch mismatch, which a lookup cannot change)
  may conclude there is no gating PR.
- A failed settings read now disables the sweep instead of falling back to
  the 3-day default, so a user with auto-settle turned off cannot get
  inactivity settles during a read failure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hority

- A cached "closed" no longer settles directly: a closed PR can be reopened,
  so it maps to closed-cached and gets the same cooldown-limited live
  verification as cached "open" before the settle fires. Only "merged" is
  terminal enough to trust from cache.
- peekChangeRequestState now requires the cached PR's headRef to match the
  thread's branch: the broadcaster caches local and remote halves
  independently, so after a branch switch the merged view can pair the new
  refName with the previous branch's PR — never settle on another branch's
  merge.
- threadLastActivityAt clamps future-stamped candidates to the sweep's
  clock, so a skewed client createdAt can no longer hold a thread "fresh"
  forever and block inactivity settle (mirrors the decider's settledAt
  clamp).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Main's new chat-header action menu (#5592) shipped against the old
client-side effectiveSettled and the removed sidebarAutoSettleAfterDays
client setting. The menu now reads the settledOverride like every other
surface, and the ChangeRequestStateLike prop threading through
ChatView → ChatHeader → useThreadActionMenu is gone — PR state no longer
feeds settled classification anywhere.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@t3dotgg
t3dotgg force-pushed the t3code/server-side-settled-logic branch from 72f1b2f to b132fb0 Compare August 7, 2026 10:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant