Skip to content

fix(runtime): stabilize session recovery and history lifecycle - #35

Merged
muggle-stack merged 18 commits into
masterfrom
fix/session-recovery-stability
Aug 26, 2026
Merged

fix(runtime): stabilize session recovery and history lifecycle#35
muggle-stack merged 18 commits into
masterfrom
fix/session-recovery-stability

Conversation

@muggle-stack

Copy link
Copy Markdown
Owner

Summary

  • preserve session identity, account ownership, fork state, and focus across reconnects and wrapper restarts
  • track Codex terminal boundaries from authoritative sources and keep oversized sessions on the shared daemon
  • reconcile history, process, plan, context, and completion projections without phantom activity or missing rows
  • harden multi-profile daemon alignment, optional-state recovery, and uncertain native deletion handling

Validation

  • .venv/bin/python -m pytest — 1932 passed, 2 skipped
  • uvx --from ruff==0.15.13 ruff check cc_remote tests deploy
  • npm --prefix web run build
  • npm --prefix web run test:reliability
  • npm --prefix web run test:history-browser — 234 passed, 20 skipped
  • npm --prefix web run lint
  • bash -n deploy/install.sh deploy/install-relay.sh deploy/install-wrapper.sh deploy/setup-vps.sh
  • shellcheck -x deploy/install.sh deploy/install-relay.sh deploy/install-wrapper.sh deploy/setup-vps.sh deploy/setup_transaction.sh
  • git diff --check

- preserve authoritative history handoffs and ordered live projections
- reconcile fork ownership, profile-scoped receipts, effort and Work context
- keep Plan and Goal controls stable across responsive layouts
- preserve live history, process state, and compaction boundaries across reconnects
- honor profile-specific model and effort defaults for new, resumed, and forked sessions
- add durable long-paste drafts and per-engine surface preferences
- align lagging profile daemons without breaking shared-session affinity
- rebuild corrupt optional projections during account topology migration
- preserve fork deletion state when native removal remains uncertain

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 294f4c9aa3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread web/src/components/Composer.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5aba31703f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread cc_remote/wrapper/machine.py Outdated
Comment thread cc_remote/wrapper/machine.py
Comment thread cc_remote/wrapper/machine.py
Comment thread web/src/chat-dialog-geometry.ts Outdated
Comment thread web/src/reducer.ts
Comment thread web/src/history-merge.ts
Comment thread web/src/App.tsx Outdated
Comment thread cc_remote/wrapper/codex_sessions.py
Comment thread cc_remote/wrapper/codex_handle.py Outdated
Comment thread web/src/compaction-orphans.ts Outdated
Comment thread web/src/history-merge.ts Outdated
if (cloned.kind === "text") {
cloned.done = true;
} else if (cloned.kind === "process" && !cloned.done
&& cloned.processKind !== "plan") {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

web/src/history-merge.ts:L486: 🟠 This unconditional Plan exemption misses the reverse IndexedDB race. A cache written from a neutral-steer segment can have cached.done=true with a running Plan. If authoritative idle History installs first and a delayed same-revision hydrate_cache follows, restoreCachedTurnDetails() reintroduces that running block after History already settled it; no later fence or frame is guaranteed to close it. Carry the current running/idle authority into cache restoration, preserving the Plan only for a running or live-raced page, and add the regression in this order: idle History first, cache second.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in b4f9da5: cached detail restoration now receives explicit enclosing-task authority. An authoritative idle History settles a delayed same-revision cached Plan, while only a newer unsettled live frame preserves it. Regressions cover History-first/cache-second and a later lifecycle terminal superseding the live frame.

Comment thread web/src/components/PasteCards.tsx Outdated
<span className="paste-card-icon"><Icon name="read" size={15} /></span>
<span className="paste-card-body">
<span className="paste-card-preview">
{paste.text.replace(/\s+/g, " ").trim()}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

web/src/components/PasteCards.tsx:L45: 🟠 The CSS ellipsis does not bound the work here: every render still scans the full paste with replace(), creates the normalized copy, and mounts the complete text node. With the supported 2 MiB prompt size, opening the editor makes every keystroke repeat this work and also split the full editingText at L73, which can freeze or exhaust memory on a phone. Slice to a small bounded preview before normalization/rendering and avoid allocating a full line array during render; cover the preview helper with a maximum-output regression.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in b4f9da5: paste cards render a 180-character preview from a bounded 1 KiB prefix, the editor is uncontrolled, and line metadata uses a debounced single-pass counter instead of allocating a full split array on every render. The 2 MiB browser regression verifies the exact bounded preview and editable payload.

- settle delayed cached plans from authoritative lifecycle state
- bind running indicators to the exact live turn across Code and BTW
- bound oversized paste previews and editor metadata work
Comment thread web/src/App.tsx
// row, another account, or a read-only browse projection.
const activeTurnId = exactActiveTurnId(
historyView.turns,
rt.liveOwner?.turnId,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟠 The authoritative State(idle) path leaves liveOwner pointing at the completed task. The wrapper emits the next State(running) before its UserMsg/TurnBinding, so this resolves task A as active while task B is launching: both rows render as working and the new completion guard hides A’s footer. Clear or generation-correlate the owner at the idle boundary, and cover idle A → query B → running State before B binding.

Comment thread web/src/reducer.ts
rt.turns = restoreCachedTurnDetails(rt.turns, action.turns);
const newerUnsettledLiveFrame = rt.lastLiveSeq
> Math.max(rt.historyLiveSeq, rt.lastLifecycleSeq);
const cacheRestoreAuthority = rt.state !== "idle"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🟠 This authority is session-wide rather than turn-scoped. A Query does not advance the History revision before it materializes, so task B can start after idle History(A) but before delayed same-revision IndexedDB hydration; rt.state is then running and restoreCachedTurnDetails() revives task A’s stale open Plan as B’s monitor. Preserve an open cached Plan only for the exactly matched active owner, and add the History(A) → query B → cache(A) ordering regression.

@muggle-stack
muggle-stack merged commit 4c7e6ce into master Aug 26, 2026
3 checks passed
@muggle-stack
muggle-stack deleted the fix/session-recovery-stability branch August 26, 2026 06:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants