Skip to content

feat: unify session todo source of truth#873

Merged
Astro-Han merged 14 commits into
devfrom
codex/i650-todo-source-of-truth
May 23, 2026
Merged

feat: unify session todo source of truth#873
Astro-Han merged 14 commits into
devfrom
codex/i650-todo-source-of-truth

Conversation

@Astro-Han
Copy link
Copy Markdown
Owner

@Astro-Han Astro-Han commented May 23, 2026

Summary

Unifies session todos behind a server-stamped { revision, todos } snapshot and one frontend canonical cache.

  • Adds persisted per-session todo revisions, including authoritative empty clears.
  • Updates REST, SSE todo.updated, todowrite metadata, OpenAPI, and SDK types to carry the same snapshot shape.
  • Routes REST, live events, and tool metadata through one revision gate plus a todo hydrate coordinator.
  • Makes status-summary todo consumers prefer canonical snapshots and use transcript parts only as render-only placeholders while the canonical cache is absent.
  • Preserves UI state distinctions for present empty canonical snapshots, pending hydrate, and authoritatively invalidated sessions.
  • Merges latest dev after refactor: retire todo composer dock #867 so the retired composer ToDo dock stays deleted.

Why

Fixes the stale todo source-of-truth split from #650: late REST/SSE/tool metadata or historical transcript parts could roll a completed or empty todo state back to active because each consumer had its own freshness heuristic.

Related Issue

Closes #650.

Human Review Status

Pending

Review Focus

Please focus on the revision contract boundaries:

  • Todo.get / Todo.update transaction behavior and migration backfill.
  • Revision-gated frontend writes from REST, todo.updated, and completed todowrite metadata.
  • Authoritative invalidation for deleted/archived/404 sessions.
  • Status-summary behavior where present empty canonical snapshots suppress legacy parts placeholders.
  • Status-summary behavior while canonical hydrate is pending: it should not render the empty progress fallback.
  • The refactor: retire todo composer dock #867 merge resolution: this PR should not restore the retired composer ToDo dock.

Risk Notes

The migration adds session_todo_revision and backfills sessions with existing todo rows to revision 1. REST 404/not-found for the current session is now authoritative and clears canonical todo plus local placeholder sources. OpenAPI/SDK generated contracts were updated for the new TodoSnapshot response/event shape.

After #867, visible todo UI in this PR is limited to the right-side Status summary; the old composer ToDo dock remains deleted.

Skipped checklist item: platform/packaging was left unticked because this does not touch platform, packaging, updater, signing, path, shell, or permission surfaces.

How To Verify

packages/opencode:
  bun script/check-migrations.ts && bun run typecheck && bun test --timeout 30000 test/session/todo.test.ts test/storage/json-migration.test.ts test/server/session-e2e-routes.test.ts
  Result: migrations are up to date; typecheck passed; 34 passed, 0 failed

packages/sdk/js:
  bun run typecheck
  Result: passed

packages/app:
  bun test --preload ./happydom.ts src/context/global-sync.test.ts src/context/global-sync/event-reducer.test.ts src/context/global-sync/todo-hydrate-coordinator.test.ts src/pages/session/todos/todo-source.test.ts src/pages/session/session-todos.test.ts src/pages/session/todos/todo-model.test.ts src/components/session/session-status-summary.test.ts src/shell-frame-contract.test.ts && bun run typecheck
  Result: 80 passed, 0 failed; typecheck passed

packages/app:
  PLAYWRIGHT_PORT=3010 bun run snap status-summary-todos && PLAYWRIGHT_PORT=3010 bun run snap todo-status-only
  Result: both snap targets passed; generated grids were visually reviewed locally

Diff check:
  git diff --check
  Result: passed before committing the review follow-up

Screenshots or Recordings

Generated and visually reviewed local snap grids:

  • docs/design/preview/screenshots/todo-status-only.png
  • docs/design/preview/screenshots/status-summary-todos.png

Checklist

How to use this checklist:

  • Tick a box by replacing [ ] with [x]. Do not edit, add, or remove items.
  • The bot-applied label items can only be honestly ticked AFTER the PR is opened and the labeler / priority-triage bots have run — return to the PR description and tick them then.
  • Most items are required. The few that are conditional are explicitly marked (conditional); for those, leave unticked if they truly do not apply and explain why in Risk Notes. All other items must be ticked before requesting human review.
  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added revision tracking to todo snapshots for improved update coordination
    • Added "pending" phase to indicate when todo data is being fetched
    • Enhanced todo synchronization with coordinated hydration management
  • Changes

    • Todo API endpoints now return snapshot objects with revision metadata instead of raw todo arrays
    • Improved todo caching strategy and invalidation handling

Review Change Stack

@Astro-Han Astro-Han added enhancement New feature or request P2 Medium priority app Application behavior and product flows ui Design system and user interface tech-debt Supplemental cleanup, maintainability, architecture, test, or quality debt context labels May 23, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 2026

Warning

Review limit reached

@Astro-Han, we couldn't start this review because you've used your available PR reviews for now.

Your plan currently allows 1 review/hour. Refill in 40 minutes and 18 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 26ef0e8a-7692-4b83-a95c-d277a51d7f34

📥 Commits

Reviewing files that changed from the base of the PR and between 30487f0 and d39c84b.

📒 Files selected for processing (9)
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/app/src/context/global-sync/todo-hydrate-coordinator.test.ts
  • packages/app/src/context/global-sync/todo-hydrate-coordinator.ts
  • packages/app/src/pages/session/use-session-refresh-effects.test.ts
  • packages/app/src/pages/session/use-session-refresh-effects.ts
  • packages/opencode/src/session/todo.ts
  • packages/opencode/test/session/todo.test.ts
📝 Walkthrough

Walkthrough

PR implements a unified session todo snapshot model with revision-gated acceptance, replaces timestamp-based freshness heuristics with explicit pending/invalidation flags, introduces a SolidJS hydration coordinator for tracking lifecycle, and refactors backend API/storage to return snapshots with monotonic revision numbers instead of arrays.

Changes

Unified Session Todo Snapshot & Hydration Model

Layer / File(s) Summary
Todo Snapshot Model & Type System
packages/app/src/pages/session/todos/todo-model.ts, packages/app/src/pages/session/todos/todo-source.ts, packages/opencode/src/session/todo.ts, packages/sdk/openapi.json
TodoPhase now includes "pending" state; TodoSourceKind expanded to include "pending" and "invalidated"; TodoSnapshot removes sourceUpdatedAt and gains explicit phase; backend introduces CanonicalTodoSnapshot with revision and todos fields.
Session Todo Source Resolution
packages/app/src/pages/session/todos/todo-source.ts, packages/app/src/pages/session/todos/todo-source.test.ts, packages/app/src/pages/session/session-todos.test.ts, packages/app/src/pages/session/todos/todo-model.test.ts
Frontend todo selection refactored to prioritize canonical backend snapshots over parts-derived fallbacks; removes backend-vs-parts reconciliation and timestamp-based freshness; adds explicit isAuthoritivelyInvalidated and isPending flags to replace heuristics; test cases updated to validate precedence and fallback routing.
Todo Hydration Coordinator
packages/app/src/context/global-sync/todo-hydrate-coordinator.ts, packages/app/src/context/global-sync/todo-hydrate-coordinator.test.ts
New SolidJS coordinator tracks pending/invalidated hydration state per (directory, session) with token-based lifecycle (beginHydrate, isCurrent, completeHydrate); manages recovery epoch validation, per-session/directory eviction limits, and transitions invalidation state when live writes reopen.
Global Sync Todo State & Acceptance Logic
packages/app/src/context/global-sync.tsx, packages/app/src/context/global-sync/bootstrap.ts, packages/app/src/context/global-sync.test.ts
GlobalStore.session_todo changes from Todo[] to SessionTodoSnapshot per session; introduces canAcceptSessionTodo and setSessionTodoSnapshot helpers for revision-gated writes; wires todoHydrate coordinator into event dispatch and adds accept/clearAuthoritative to public todo API.
Event Reducer Todo Acceptance Gating
packages/app/src/context/global-sync/event-reducer.ts, packages/app/src/context/global-sync/event-reducer.test.ts
Adds todoSnapshotFromPart and todoSnapshotFromProperties helpers to construct snapshots from events; introduces acceptLiveTodo gate using todoHydrate.canAcceptLiveTodo and optional acceptSessionTodo callback; refactors detached and active directory event handlers to check acceptance before updating store; replaces setSessionTodo wiring with explicit acceptSessionTodo, clearSessionTodoAuthoritative, and todoHydrate callbacks.
Session Sync Hydration & Eviction
packages/app/src/context/sync.tsx, packages/app/src/pages/layout.tsx, packages/app/src/pages/session.tsx
Rewrites session.todo() to use beginHydrate/completeHydrate lifecycle with token-based stale prevention, fetches authoritative snapshot with revision-based acceptance, and handles not-found errors with authoritative invalidation; replaces in-memory seen cache eviction map with globalSync.todoHydrate.touch()/has()/invalidate() APIs; simplifies stale-prefetch cleanup.
Backend Todo Snapshot & Revision Tracking
packages/opencode/migration/20260523120000_session_todo_revision/migration.sql, packages/opencode/src/session/session.sql.ts, packages/opencode/src/session/todo.ts, packages/opencode/test/session/todo.test.ts, packages/opencode/src/storage/json-migration.ts, packages/opencode/test/storage/json-migration.test.ts, packages/opencode/src/tool/todo.ts, packages/opencode/src/server/instance/session.ts, packages/sdk/openapi.json
Creates SessionTodoRevisionTable with session-scoped monotonic revision (seeded to 1 for existing sessions); updates Todo.update() and Todo.get() to return Snapshot shape with revision; increments revision transactionally on each write after resolving todo IDs; updates migration tooling to seed revision table; changes API response schema and Event.Updated payload to include revision.
Status Panel & Summary UI Wiring
packages/app/src/components/session/session-status-panel.tsx, packages/app/src/components/session/session-status-summary.tsx, packages/app/src/components/session/session-status-summary.test.ts, packages/app/src/pages/session/use-session-refresh-effects.ts, packages/app/src/pages/session/use-session-refresh-effects.test.ts
SessionStatusPanel computes and passes canonical, isAuthoritivelyInvalidated, isPending accessors to SessionStatusSummary; SessionStatusSummary uses selectSessionTodoDataSnapshot and hides progress section during pending hydration via Show when={snapshot().phase !== "pending"}; useSessionRefreshEffects accepts new hydration scheduling/cancellation hooks and recovery epoch metadata; adds test validating initial visible hydrate scheduling before frame boundary.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • Astro-Han/pawwork#381: Modifies SessionStatusPanel/SessionStatusSummary todo source extraction in the same code path.
  • Astro-Han/pawwork#484: Extends detached-directory todo caching in event-reducer.ts that this PR migrates to snapshot/hydrate model.
  • Astro-Han/pawwork#637: Prior todo source override/clearing logic in Status UI that this PR unifies under canonical snapshot model.

Suggested labels

P1, upstream

Poem

🐰 Hop along the snapshot trail,
Revisions track what parts entail,
One canonical truth now guides the way,
No more heuristics' uncertain sway.
Fresh hydrates bloom in pending grace,
While stale parts yield their rightful place.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% 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
Title check ✅ Passed Title clearly summarizes the main objective: unifying session todo state behind a single server-stamped snapshot source of truth, which is the central theme of all changes.
Description check ✅ Passed Description is comprehensive and complete. It includes Summary, Why, Related Issue, Human Review Status, Review Focus, Risk Notes, How To Verify with actual results, Screenshots, and all checklist items properly addressed.
Linked Issues check ✅ Passed The PR addresses all primary objectives from #650: unifies todo state via canonical SessionTodoSnapshot [#650], routes all sources through revision-gated resolver [#650], preserves freshness semantics with backend revision tracking [#650], removes selector-level heuristics by using canonical snapshots [#650], and adds comprehensive test coverage [#650].
Out of Scope Changes check ✅ Passed All changes are in-scope with #650: database schema and migration for revisions, backend snapshot model, frontend canonical cache and hydrate coordinator, revision-gated event handling, and consumer refactoring to use canonical snapshots. No unrelated refactors, styling changes, or composer dock restoration detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/i650-todo-source-of-truth

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 and usage tips.

@github-actions github-actions Bot added the harness Model harness, prompts, tool descriptions, and session mechanics label May 23, 2026
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Suggested priority: P2 (includes user-path files (packages/app/src/components/session/session-status-panel.tsx, packages/app/src/components/session/session-status-summary.tsx, packages/app/src/context/global-sync.test.ts, packages/app/src/context/global-sync.tsx, packages/app/src/context/global-sync/bootstrap.ts, packages/app/src/context/global-sync/event-reducer.test.ts, packages/app/src/context/global-sync/event-reducer.ts, packages/app/src/context/global-sync/todo-hydrate-coordinator.test.ts, packages/app/src/context/global-sync/todo-hydrate-coordinator.ts, packages/app/src/context/sync.tsx, packages/app/src/pages/layout.tsx, packages/app/src/pages/session.tsx, packages/app/src/pages/session/session-todos.test.ts, packages/app/src/pages/session/todos/todo-dock-machine.test.ts, packages/app/src/pages/session/todos/todo-dock-machine.ts, packages/app/src/pages/session/todos/todo-model.ts, packages/app/src/pages/session/todos/todo-source.test.ts, packages/app/src/pages/session/todos/todo-source.ts, packages/app/src/pages/session/todos/use-session-todos.test.ts, packages/app/src/pages/session/todos/use-session-todos.ts, packages/app/src/pages/session/use-session-refresh-effects.ts)).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a revision-based synchronization system for session todos, moving from simple array updates to a snapshot model that includes a monotonic revision number. Key changes include the addition of a session_todo_revision table in the backend, updates to the SDK and API to support TodoSnapshot, and the introduction of a TodoHydrateCoordinator in the frontend to manage authoritative invalidation and recovery epochs. Feedback focuses on ensuring that archived sessions are handled correctly in the API to avoid unintended UI invalidation, optimizing the cleanup of invalidated sessions across directory stores, and improving the efficiency of the frontend store reconciliation by using stable todo IDs.

Comment thread packages/opencode/src/session/todo.ts Outdated
Comment thread packages/app/src/context/global-sync/todo-hydrate-coordinator.ts
Comment thread packages/app/src/context/global-sync.tsx Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 23, 2026

Perf delta summary

Comparator: pass

Profile / Scenario interaction median interaction worst long task max tbt frame gap p95 frame gap max jank count cls status
default / homepage-cold 32 -> 32 (0) 32 -> 32 (0) 74 -> 67 (-7) 24 -> 17 (-7) 49.9 -> 33.4 (-16.5) 133.4 -> 116.7 (-16.7) 4 -> 3 (-1) 0 -> 0 (0) pass
default / long-session-input-lag 48 -> 40 (-8) 48 -> 48 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-streaming-long 40 -> 40 (0) 72 -> 64 (-8) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 16.8 -> 33.3 (+16.5) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-call-expand 24 -> 16 (-8) 24 -> 24 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.7 -> 16.7 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-default-open-heavy-bash 16 -> 16 (0) 16 -> 16 (0) 65 -> 73 (+8) 15 -> 39 (+24) 50 -> 33.4 (-16.6) 133.3 -> 166.7 (+33.4) 2 -> 2 (0) 0 -> 0 (0) pass
default / terminal-side-panel-open 40 -> 48 (+8) 48 -> 48 (0) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.3 (0) 33.3 -> 33.3 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-scroll-reading 16 -> 16 (0) 16 -> 16 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.8 (+0.1) 16.7 -> 16.8 (+0.1) 0 -> 0 (0) 0 -> 0 (0) pass
low-end / session-scroll-reading-long 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.3 (0) 50 -> 33.5 (-16.5) 0 -> 0 (0) 0 -> 0 (0) pass
low-end / session-timeline-recompute 32 -> 32 (0) 32 -> 40 (+8) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.3 (0) 33.4 -> 33.3 (-0.1) 0 -> 0 (0) 1.075 -> 1.075 (0) pass
low-end / concurrent-shimmer-extreme 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.7 (-0.1) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
packages/opencode/test/session/todo.test.ts (1)

58-217: 🏗️ Heavy lift

Prefer testEffect + it.live for these new Effect service tests.

These added cases run Effect services and live filesystem/git behavior, so they should be migrated to the standard test harness to match repo test runtime semantics.

As per coding guidelines, “Use testEffect(...) from test/lib/effect.ts for tests that exercise Effect services or Effect-based workflows” and “Use it.live(...) when the test depends on real time, filesystem mtimes, child processes, git, locks, or other live OS behavior.”

🤖 Prompt for 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.

In `@packages/opencode/test/session/todo.test.ts` around lines 58 - 217, Replace
the plain Jest async tests that exercise Effect services and live FS/git
behavior (the tests with titles "update returns a revisioned snapshot and get
persists it", "second update preserves id and persists status changes", "empty
clear bumps revision and stays authoritative", "get returns rev0 empty only for
known sessions that never had todos", "get reads archived sessions while update
rejects them", and "get rejects unknown sessions") with the standard test
harness by importing testEffect and it from test/lib/effect and wrapping each
test body with testEffect(it.live(async () => { ... })), keeping the existing
contents (Instance.provide, tmpdir usage, Todo.Service.use calls,
Session.create/remove, etc.) unchanged; update top-of-file imports to include {
testEffect, it } so the new test declarations compile.
🤖 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 `@packages/app/src/context/global-sync.tsx`:
- Around line 257-260: The session-trim branch currently clears child caches via
cleanupDroppedSessionCaches(store, setStore, next) but doesn't notify the
hydrate coordinator about removed session IDs; replicate the earlier pattern
used elsewhere by computing the removed IDs (e.g. compare store.session IDs to
next IDs) and call the todoHydrate "forget trimmed" API with those IDs
immediately after setStore/reconcile and cleanupDroppedSessionCaches (same
change should be applied at the other trim site around lines 294-296); reference
functions: reconcile, setStore, store.session, cleanupDroppedSessionCaches, and
todoHydrate when adding the notification call.

In `@packages/app/src/context/global-sync/todo-hydrate-coordinator.ts`:
- Around line 79-103: touch() currently evicts session IDs from the seen map but
does not fully clear per-session hydrate state, so pending-only sessions keep
stale validatedRecovery/tokenEpoch and can be missed by invalidateSession();
modify touch() (and the similar logic around lines 148-159) to call the same
cleanup used by forgetSession() (i.e. remove pending entries and clear
validatedRecovery and tokenEpoch for that (directory, sessionID)) whenever a
session is evicted or a directory is removed; ensure you reference and update
the pending map, validatedRecovery and tokenEpoch stores (and call
removeDirectoryState or the equivalent cleanup helper) for each evicted
sessionID so reopened sessions cannot inherit stale state.

In `@packages/app/src/pages/session/use-session-refresh-effects.ts`:
- Around line 166-206: Add input.validatedRecoveryEpoch to the on(...) source
tuple so the effect reruns when validated recovery changes: in the
source-returning function include id ?
(input.validatedRecoveryEpoch?.(input.directory(), id) ?? 0) : 0 as an extra
tuple element, then destructure that new value in the callback (e.g.
recoveryValidatedFromSource) and use it instead of recomputing recoveryValidated
inside the callback; this ensures recoveryDue is computed from the fresh
validated value and any pending scheduledTodo/todoFrame/todoTimer will be
cancelled/updated accordingly when validatedRecoveryEpoch changes (references:
input.validatedRecoveryEpoch, scheduledTodo, todoFrame, todoTimer,
syncTodoWithDiagnostics).

In `@packages/opencode/src/session/todo.ts`:
- Around line 99-109: The current pattern uses readSnapshot to validate the
session's active state in a separate transaction but performs the mutation
later, allowing a TOCTOU where the session can be archived between transactions;
fix by performing the active-check and the subsequent todo write inside the same
Database.transaction (or use a SELECT FOR UPDATE/row lock) so the code that
reads SessionTable (SessionTable.id / SessionTable.time_archived) and throws
NotFoundError runs in the exact transaction that performs the update (e.g., move
the active check into the transaction block used by update/updateTodo or combine
readSnapshot logic into the update transaction), ensuring the write only
proceeds when the session is confirmed active.

---

Nitpick comments:
In `@packages/opencode/test/session/todo.test.ts`:
- Around line 58-217: Replace the plain Jest async tests that exercise Effect
services and live FS/git behavior (the tests with titles "update returns a
revisioned snapshot and get persists it", "second update preserves id and
persists status changes", "empty clear bumps revision and stays authoritative",
"get returns rev0 empty only for known sessions that never had todos", "get
reads archived sessions while update rejects them", and "get rejects unknown
sessions") with the standard test harness by importing testEffect and it from
test/lib/effect and wrapping each test body with testEffect(it.live(async () =>
{ ... })), keeping the existing contents (Instance.provide, tmpdir usage,
Todo.Service.use calls, Session.create/remove, etc.) unchanged; update
top-of-file imports to include { testEffect, it } so the new test declarations
compile.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 095fc6a5-6155-452a-82b3-68c95ff2f007

📥 Commits

Reviewing files that changed from the base of the PR and between 79c79e9 and 30487f0.

⛔ Files ignored due to path filters (2)
  • packages/sdk/js/src/gen/types.gen.ts is excluded by !**/gen/**
  • packages/sdk/js/src/v2/gen/types.gen.ts is excluded by !**/gen/**
📒 Files selected for processing (30)
  • packages/app/src/components/session/session-status-panel.tsx
  • packages/app/src/components/session/session-status-summary.test.ts
  • packages/app/src/components/session/session-status-summary.tsx
  • packages/app/src/context/global-sync.test.ts
  • packages/app/src/context/global-sync.tsx
  • packages/app/src/context/global-sync/bootstrap.ts
  • packages/app/src/context/global-sync/event-reducer.test.ts
  • packages/app/src/context/global-sync/event-reducer.ts
  • packages/app/src/context/global-sync/todo-hydrate-coordinator.test.ts
  • packages/app/src/context/global-sync/todo-hydrate-coordinator.ts
  • packages/app/src/context/sync.tsx
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/session.tsx
  • packages/app/src/pages/session/session-todos.test.ts
  • packages/app/src/pages/session/todos/todo-model.test.ts
  • packages/app/src/pages/session/todos/todo-model.ts
  • packages/app/src/pages/session/todos/todo-source.test.ts
  • packages/app/src/pages/session/todos/todo-source.ts
  • packages/app/src/pages/session/use-session-refresh-effects.test.ts
  • packages/app/src/pages/session/use-session-refresh-effects.ts
  • packages/opencode/migration/20260523120000_session_todo_revision/migration.sql
  • packages/opencode/src/server/instance/session.ts
  • packages/opencode/src/session/session.sql.ts
  • packages/opencode/src/session/todo.ts
  • packages/opencode/src/storage/json-migration.ts
  • packages/opencode/src/storage/schema.ts
  • packages/opencode/src/tool/todo.ts
  • packages/opencode/test/session/todo.test.ts
  • packages/opencode/test/storage/json-migration.test.ts
  • packages/sdk/openapi.json
💤 Files with no reviewable changes (2)
  • packages/app/src/pages/layout.tsx
  • packages/app/src/pages/session/todos/todo-model.test.ts

Comment thread packages/app/src/context/global-sync.tsx
Comment thread packages/app/src/context/global-sync/todo-hydrate-coordinator.ts
Comment thread packages/app/src/pages/session/use-session-refresh-effects.ts
Comment thread packages/opencode/src/session/todo.ts Outdated
@Astro-Han Astro-Han merged commit e316fd9 into dev May 23, 2026
27 checks passed
@Astro-Han Astro-Han deleted the codex/i650-todo-source-of-truth branch May 23, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows enhancement New feature or request harness Model harness, prompts, tool descriptions, and session mechanics P2 Medium priority tech-debt Supplemental cleanup, maintainability, architecture, test, or quality debt context ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Unify session todo source of truth and freshness model

1 participant