Skip to content

feat(threads): auto-archive settled threads - #13657

Open
voltcrash wants to merge 2 commits into
pingdotgg:mainfrom
voltcrash:t3code/auto-archive-settled-threads
Open

voltcrash wants to merge 2 commits into
pingdotgg:mainfrom
voltcrash:t3code/auto-archive-settled-threads

Conversation

@voltcrash

@voltcrash voltcrash commented Sep 25, 2026 •

Copy link
Copy Markdown

Settled threads pile up forever. Auto-settle clears finished work out of the active list, but the settled shelf keeps growing (hundreds of threads on a busy install) until someone archives them one by one.

This adds Auto-archive settled threads to Settings → General → Organization, under the auto-settle rows. It is off by default. Turning it on uses 30 days, and you can change the number. A thread archives once it has been settled for that many days. Archived threads stay in Settings → Archive and can be restored as usual.

How it works

  • Contracts: new sidebarAutoArchiveAfterDays server setting (null = off, 1–90 days, same bounds as auto-settle). It is project-scoped like the auto-settle settings, so a project can override or disable it. There's also a new threadAutoArchive capability so clients hide the controls on older servers, which would silently ignore the key.
  • Server: ThreadSettlementReactor already runs a sweep every minute and on settlement-setting changes. After the settle pass, it archives threads that are still settled past their resolved threshold. It dispatches a new internal thread.auto-archive command that carries the settledAt it observed. The decider rejects the command if the thread was un-settled or re-settled after the snapshot, so new activity can never race into an archive. It emits the existing thread.archived event, so projection and clients need no changes.
  • Restore: when an archived settled thread is restored, thread.unarchived gives it a fresh settledAt. The thread gets a new full window, and any auto-archive queued with the old value fails the guard.
  • Clients: web/desktop rows (toggle plus a days field, with reset, inheritance, restore-defaults, and settings search), mobile Thread behavior → Auto-archive section, and shared-settings sync that skips targets without the capability.
  • Docs: one paragraph in docs/user/thread-sidebar.md.

Providers are unaffected.

Screenshots (2880×1800)

Before After (default, off) After (on)
before after, off after, on

Testing

  • New decider tests for thread.auto-archive: archives when still settled at the observed time; rejects un-settled, activity-cleared, re-settled, and already-archived threads.
  • isAutoArchiveDue policy tests, plus a reactor test covering off-by-default, the threshold, and a project override that disables it.
  • Shared-settings test for capability gating.
  • Checked end to end against a copy of a real database (225 settled threads): at 30 days nothing was due, so nothing archived. At 10 days, exactly the 71 threads settled longer than that were archived, the 154 newer ones stayed, and nothing was logged as a warning.
  • Typecheck is clean for contracts, shared, client-runtime, server, web, and mobile. The storage cleanup cases in ThreadSettlementReactor.test.ts fail the same way on untouched main in my environment.

Model: Claude Opus 5.5 (1M context) · Harness: Claude Code in T3 Code

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Settled threads can now be automatically archived after a configurable number of days. Auto-archiving is off by default and can be configured in settings when supported by connected environments.
    • Restoring a settled thread starts a fresh countdown before it can be automatically archived again.
  • Documentation
    • Updated the thread sidebar guide with auto-archive behavior and restoration details.

Adds an off-by-default "Auto-archive settled threads" setting (30 days when
enabled) to Settings → General → Organization and mobile Thread behavior.
The settlement reactor archives threads that stayed settled past the
threshold through a new internal thread.auto-archive command, which the
decider rejects if the thread was un-settled or re-settled since the
snapshot. The setting is project-scoped, synced like the auto-settle
preferences, and gated by a threadAutoArchive server capability.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Sep 25, 2026
command,
threadId: command.threadId,
});
if (thread.settledOverride !== "settled" || thread.settledAt !== command.settledAt) {

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 orchestration/decider.ts:464

A queued thread.auto-archive can archive a thread after the user restores it. The guard only compares settledOverride and settledAt, while thread.unarchived leaves both values unchanged, so a snapshot taken before archive/restore still passes and re-archives the restored thread. Include an archive/lifecycle generation in the snapshot guard or invalidate the pending auto-archive on restore.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/decider.ts around line 464:

A queued `thread.auto-archive` can archive a thread after the user restores it. The guard only compares `settledOverride` and `settledAt`, while `thread.unarchived` leaves both values unchanged, so a snapshot taken before archive/restore still passes and re-archives the restored thread. Include an archive/lifecycle generation in the snapshot guard or invalidate the pending auto-archive on restore.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Confirmed, and it was broader than the queued-command race: thread.unarchived left settledAt untouched, so even without a race the next one-minute sweep would re-archive a restored thread immediately.

Fixed in 84ec6da. When the restored thread is settled, thread.unarchived now carries a fresh settledAt. The projector, projection, and client reducer apply it, so:

  • a restored thread gets a new full window before it can auto-archive again
  • any thread.auto-archive queued before the archive/restore carries the old settledAt and is rejected by the existing guard

Restoring an active thread is unchanged. Covered by new decider and projector tests.

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.

Sorry, I'm unable to act on this request because you do not have permissions within this repository.

@macroscopeapp

macroscopeapp Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR introduces a substantial cross-layer capability that automatically archives user threads and adds a new 30-day enablement default. An unresolved lifecycle race can re-archive a restored thread, so the production behavior requires human review.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Unarchiving left settledAt unchanged, so the next settlement sweep archived
a restored thread again, and a thread.auto-archive queued before an
archive/restore still matched its settledAt guard. thread.unarchived now
carries a fresh settledAt when the thread is settled; the projector,
projection, and client reducer apply it. Restores get a new window and any
stale auto-archive command is rejected by the existing guard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 62e8d3c6-a458-4236-9a96-26efb0b18338

📥 Commits

Reviewing files that changed from the base of the PR and between e5a46d6 and 84ec6da.

📒 Files selected for processing (24)
  • apps/mobile/src/features/settings/SettingsThreadsRouteScreen.tsx
  • apps/mobile/src/features/settings/components/AutoSettleDaysField.android.tsx
  • apps/mobile/src/features/settings/components/AutoSettleDaysField.ios.tsx
  • apps/mobile/src/features/settings/components/AutoSettleDaysField.tsx
  • apps/server/src/environment/ServerEnvironment.ts
  • apps/server/src/orchestration/Layers/ProjectionPipeline.ts
  • apps/server/src/orchestration/ThreadSettlementPolicy.test.ts
  • apps/server/src/orchestration/ThreadSettlementPolicy.ts
  • apps/server/src/orchestration/ThreadSettlementReactor.test.ts
  • apps/server/src/orchestration/ThreadSettlementReactor.ts
  • apps/server/src/orchestration/decider.autoArchive.test.ts
  • apps/server/src/orchestration/decider.ts
  • apps/server/src/orchestration/projector.test.ts
  • apps/server/src/orchestration/projector.ts
  • apps/web/src/components/settings/SettingInheritance.tsx
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/settings/settingsSearch.ts
  • docs/user/thread-sidebar.md
  • packages/client-runtime/src/state/sharedSettings.test.ts
  • packages/client-runtime/src/state/sharedSettings.ts
  • packages/client-runtime/src/state/threadReducer.ts
  • packages/contracts/src/environment.ts
  • packages/contracts/src/orchestration.ts
  • packages/contracts/src/settings.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The change adds an off-by-default setting for automatically archiving settled threads after a configurable number of days. It adds capability-aware web and mobile controls, server-side eligibility checks and archive commands, and resets the settled-time clock when a settled thread is restored.

Changes

Settled-thread auto-archive

Layer / File(s) Summary
Settings, capability, and command contracts
packages/contracts/src/settings.ts, packages/contracts/src/environment.ts, packages/contracts/src/orchestration.ts, packages/client-runtime/src/state/sharedSettings.ts, packages/client-runtime/src/state/sharedSettings.test.ts
Adds the nullable archive-delay setting and its 30-day default, the server capability and command schemas, and capability-aware shared-setting filtering.
Archive eligibility and sweeps
apps/server/src/environment/ServerEnvironment.ts, apps/server/src/orchestration/ThreadSettlementPolicy.ts, apps/server/src/orchestration/ThreadSettlementPolicy.test.ts, apps/server/src/orchestration/ThreadSettlementReactor.ts, apps/server/src/orchestration/ThreadSettlementReactor.test.ts
Adds due-thread checks and a sweep that uses project settings to dispatch archive commands. The sweep tests cover disabled, recently settled, active, and project-opt-out cases.
Archive command and restoration lifecycle
apps/server/src/orchestration/decider.ts, apps/server/src/orchestration/decider.autoArchive.test.ts, apps/server/src/orchestration/Layers/ProjectionPipeline.ts, apps/server/src/orchestration/projector.ts, apps/server/src/orchestration/projector.test.ts, packages/client-runtime/src/state/threadReducer.ts
Validates archive commands against current settled state and applies the refreshed settledAt value from unarchive events where provided.
Web and mobile auto-archive settings
apps/web/src/components/settings/SettingsPanels.tsx, apps/web/src/components/settings/SettingInheritance.tsx, apps/web/src/components/settings/settingsSearch.ts, apps/mobile/src/features/settings/SettingsThreadsRouteScreen.tsx, apps/mobile/src/features/settings/components/AutoSettleDaysField*, docs/user/thread-sidebar.md
Adds capability-gated settings controls, day-count labels, search entries, and documentation for auto-archive behavior.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant ThreadSettlementReactor
  participant isAutoArchiveDue
  participant Decider
  ThreadSettlementReactor->>isAutoArchiveDue: Check thread age against the resolved archive delay
  isAutoArchiveDue-->>ThreadSettlementReactor: Return eligibility
  ThreadSettlementReactor->>Decider: Dispatch thread.auto-archive with settledAt
  Decider-->>ThreadSettlementReactor: Accept or reject based on the current settled state
Loading

Suggested reviewers: juliusmarminge, t3dotgg

Merge Risk: ⚪ Minimal · up to 84ec6

The auto-archive setting can be merged after normal checks; no outstanding behavior issue was established.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to 84ec6

Auto-archive is off by default and archived threads can be restored. However, an archive already queued under an enabled setting can still complete after that setting is turned off or its delay is increased. The impact is limited to eligible settled threads, but the setting may not take effect immediately for work in flight.

Retained concerns

  • Medium · reliability · inferred: An archive selected under an enabled project policy can commit after that policy is disabled or its delay is extended, because the command consumer rechecks settlement state but not current policy.
Security review details

Security Blast Radius

  • inferred — When enabled globally, the sweep can select eligible threads across projects; explicit project overrides determine each thread’s effective delay. The visible outcome is archival, not deletion.

Trust Boundaries and Controls

  • observed — The checked client command contract excludes the new internal command. For server-generated commands, the settlement timestamp guards against changed thread state, but is not a version of the settings policy.

Resilience and Maintainability Implications

  • observed — Restore refreshes the settlement timestamp for settled threads, preventing a queued command bearing the old timestamp from archiving the restored thread.

Hardening Proposals

  • proposed — If turning off or lengthening the policy must stop work already in flight, version the resolved policy or revalidate it at the archive commit boundary. Define explicitly whether commands selected under an earlier policy may complete.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 23 files. (1 skipped:… 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 and concisely identifies the main change: adding automatic archival for settled threads.
Description check ✅ Passed The description explains what changed, why it was needed, implementation details, UI changes with before-and-after screenshots, and testing results. It does not include the template's explicit Checkli…
Full details: Docstring Coverage

Explanation

Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 20 functions across 23 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

This branch has not been deployed

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

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant