Skip to content

feat(session): preserve Claude turns and improve live session controls - #44

Merged
muggle-stack merged 17 commits into
masterfrom
feat/session-continuity
Sep 18, 2026
Merged

muggle-stack merged 17 commits into
masterfrom
feat/session-continuity

Conversation

@muggle-stack

@muggle-stack muggle-stack commented Sep 17, 2026

Copy link
Copy Markdown
Owner

Wrapper reconnects and autonomous Claude replies can lose live-turn ownership or expose internal recovery prompts. This PR adds an optional independent Claude session service so accepted turns and pending questions survive reconnects without resubmitting prompts, and native steering preserves message, attachment and usage ownership.

  • Restore compaction boundaries, background replies and streaming subagent details; hide internal recovery messages and retain native completion state.
  • Recover failed Claude service callbacks with capped backoff. Answer retries reuse the completed result and request ID, preserving native cancellation and controller replacement.
  • Restore reachable service sessions independently. Validate identities before attachment and retry only closing controller leases on their original workers. Bound retries, propagate cancellation and preserve legacy reconnect behavior.
  • Retain failed background notifications and results for controller replay. Stop cumulative ACKs at delivery failures and defer human commits until background callbacks finish, without restarting native work or resubmitting prompts.
  • Keep steering uploads alive across controller detach and uncertain failures, then remove them after confirmed native teardown or exact terminal acknowledgement, including closure before echo replay.
  • Present readable thinking and structured tool output, preview Mermaid artifacts, and use subdued progress text and click-to-open usage details.
  • Add native per-turn token counts, BTW attachments and per-chat Codex service tiers, receipt-backed timed messages, and muted themes with a persistent bold-text preference.
  • Align TUI replay replacement and queue recovery under protocol v71.
  • Keep automatic PR/master CI to pytest and the Web build. Remove Playwright from CI and the required local PR gate; retain browser tests for requested diagnostics.

Protocol v71 requires Relay, Wrapper, Web and TUI to be upgraded together. The independent Claude service is opt-in; existing in-process turns must finish before first migration. The SDK stays pinned to 0.2.151 and uses the daily Claude Code CLI (2.1.263 or newer).

Validation on the PR head with Node 24.21.0 and Python 3.13.5:

  • Pytest: 4627 passed, 4 skipped; includes callback retry, per-service recovery, controller lease handoff, background delivery ordering and steering attachment lifetime regressions.
  • Web build, reliability suite and lint: passed.
  • Ruff 0.15.13, shell syntax, ShellCheck and diff checks: passed.
  • Playwright was not run under the revised CI/local PR policy.

- Keep Claude compaction and context usage aligned with native boundaries.
- Preserve account-scoped controls and suppress phantom Codex sessions.
- Track receipt-backed timed messages and display their sidebar activity.
- Keep sidebar menus visible above mobile and footer boundaries.
- Cover state transitions and UI behavior with regression tests and docs.
- Move active tasks into a compact composer indicator with desktop previews and centered mobile details.
- Follow native task completion, preserve detail actions, and cover keyboard focus and task lifecycle behavior.
- Set the native thinking display option for new, resumed and private-fork sessions.
- Preserve native thinking mode, token budget and effort.
- Cover native SDK launch arguments and connection lifecycle regressions.
- Route child task updates to their agent and prevent replayed completions from reopening sessions.
- Bind autonomous replies to the originating turn and preserve source completion times.
- Rebuild affected Claude history projections and cover managed, live, and recovery paths.
- Add six muted palettes with per-engine preferences and a responsive theme picker.
- Add a persistent bold text toggle that preserves typography hierarchy across dialogs and engines.
- Include a static theme preview and desktop, mobile, storage, and contrast regressions.
- Raise reading weights while preserving emphasis and font metrics.
- Add a subtle text stroke without changing SVG icon geometry.
- Cover cross-tab preferences, engine switches, and storage failures.
- Send image and file payloads through query, steer, and deferred delivery.
- Scope drops and clipboard imports to the intended draft across tab switches.
- Keep attachment-only sends distinct from interruption and respect input locks.
- Add Codex service-tier controls scoped to the side chat.
- Normalize Claude and Codex usage with exact turn ownership.
- Preserve bounded replace-only readings across replay and reconnects.
- Show live input, output, and cache counts beside the working indicator.
- Add accounting and browser regressions and coordinate protocol v68.
- Preserve internal Claude recovery boundaries and rebuild stale cached history.
- Show autonomous continuations and stream subagent details with bounded retries.
- Present readable command and tool results with raw payloads available on demand.
- Preview Mermaid artifacts and align muted process shimmer with response text.
- Keep token counts immediate and open usage details only on click or tap.
- Validate the release snapshot with the complete local gate and the development branch with 166 Python regressions and Web reliability checks.
- Recover accepted turns and pending questions through an optional independent SDK service.
- Support priority-next steering with exact native message, attachment, and usage ownership.
- Reserve private BTW identities, refresh live context, and retry busy session-list reads.
- Replace replayed text prefixes and bound history summaries with protocol v70.
- Replace replayed message prefixes in the terminal projection while preserving later deltas.
- Use protocol v71 for the combined queue reordering and session recovery schema.
- Synchronize deployment references and exercise Linux service guards on every test host.
- Keep automatic checks for pull requests and pushes to master.
- Remove Playwright from CI and the required local PR gate.
- Keep lint and reliability checks local and align release workflow coverage.

@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: 8b3b8c4dcc

ℹ️ 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 on lines +202 to +203
if key not in self.callback_tasks:
self.callback_tasks[key] = asyncio.create_task(self._callback(call))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Retry failed persistent-service callback tasks

When a permission/MCP handler raises, or the answer RPC times out while the connection remains open, _callback finishes exceptionally but its entry remains in callback_tasks. Subsequent polls include that ID in known, so the still-pending server callback is never returned and the native Claude turn waits indefinitely. Remove failed/completed tasks or exclude them from known so the pending callback can be retried.

Useful? React with 👍 / 👎.

- Retry callback failures with capped backoff while preserving cancellation.
- Reuse completed callback results and request IDs after answer timeouts.
- Cover permission and MCP retries, independent callbacks, and controller replacement.

@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: eb53584888

ℹ️ 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/claude_service.py Outdated
Comment on lines +85 to +86
for socket, item in sessions:
await _restore_session(machine, socket, item)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Continue restoring sessions after an isolated failure

When one listed service session cannot be reattached—for example, its transcript is unavailable and _spawn() returns None_restore_session raises here and aborts this entire loop; WrapperMachine.start() catches the exception only outside restore(), so every later healthy session remains controller-less, including sessions with already accepted turns. Isolate and log per-session attachment failures after the global duplicate scan so remaining sessions are still restored.

AGENTS.md reference: AGENTS.md:L44-L50

Useful? React with 👍 / 👎.

- Continue restoring healthy sessions after an individual attachment fails.
- Preserve global identity validation and cancellation propagation.
- Cover accepted-turn and permission recovery without resubmitting queries.

@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: 17d9b26e02

ℹ️ 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/claude_service.py Outdated
sessions = []
identities = set()
for socket in sockets:
for item in await _list_sessions(socket):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Continue recovery when one service socket is unavailable

When both primary and drain sockets are configured, a stale or temporarily unavailable drain socket makes this uncaught _list_sessions() call abort restore() before the healthy primary socket is inspected; likewise, a primary failure prevents already-listed drain sessions from reaching the attachment loop. Because startup invokes this recovery only once, accepted turns on the healthy service remain controller-less until another Wrapper restart. Isolate listing failures per socket and continue with sessions from reachable services.

AGENTS.md reference: AGENTS.md:L44-L50

Useful? React with 👍 / 👎.

Comment on lines +51 to +53
if directory:
ctx.claude_steer_attachment_dirs.append(directory)
await ctx.sdk.steer(prompt, native_id=native_id, metadata=metadata)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Clean steering attachments after abnormal turn exits

When a file-bearing steer is accepted or has an unknown delivery result, this directory is retained in claude_steer_attachment_dirs, but claude_steer.cleanup() is called only on the normal authoritative-Result path. If the turn instead exits through a reader/control failure or drain timeout and the session is then closed or evicted, the uploaded files remain in the temporary directory indefinitely. Add cleanup to abnormal terminal/session teardown paths while preserving files across an ordinary persistent-service detach.

Useful? React with 👍 / 👎.

- Isolate unavailable service sockets while preserving identity and cancellation checks.
- Clean accepted steering files after confirmed native teardown or exact terminal acknowledgement.
- Preserve uploads across service detach and cover recovery and cleanup with regressions.

@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: b6610c4e30

ℹ️ 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 on lines +95 to +101
try:
await _restore_session(machine, socket, item)
except Exception as exc:
# All reachable services passed the identity check. A failed session
# must not strand other sessions' already accepted turns.
log.warning("persistent Claude session recovery failed",
service_id=item.get("id"), error_type=type(exc).__name__)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Retry sessions while the old controller lease is closing

During an ordinary Wrapper restart, the old client only closes its Unix socket and does not receive an acknowledgment that the service has cleared session.controller; the replacement can therefore list the session and attempt _spawn() before the old connection's finally block runs, causing open to return “already has a controller.” This catch only logs that failure, so the already-accepted turn continues without a controller until another Wrapper restart. Retry this specific lease-conflict briefly or add an atomic detach handshake before treating recovery as failed.

AGENTS.md reference: AGENTS.md:L44-L48

Useful? React with 👍 / 👎.

- Retry explicit worker attachment conflicts within a bounded window without taking over live controllers.
- Negotiate strict worker identity and preserve older service and controller reconnect behavior.
- Keep cancellation and unknown-response failures terminal and verify handoff over real Unix sockets.

@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: 572e5c4d06

ℹ️ 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/sdk.py
Comment on lines 1687 to +1688
await callback(message, turn_id)
await self.ack_service_message(message)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve failed background events before advancing the ACK

When background_message_callback throws for one service-journaled frame, this worker catches the error and continues without acknowledging that frame; however, the next successful frame calls ack_service_message() with a higher sequence number, and Session.ack() cumulatively prunes every journal row through that sequence. A transient projection or relay failure can therefore permanently discard a task notification or background result that reattachment was supposed to recover. Stop processing after the failure or track contiguous acknowledgements so later successes cannot skip the failed sequence.

AGENTS.md reference: AGENTS.md:L44-L50

Useful? React with 👍 / 👎.

- Stop background projection and cumulative acknowledgements after a service delivery failure.
- Defer human terminal commits until pending background callbacks finish.
- Require controller replay before new input without restarting native work.
- Cover callback, ACK, terminal ordering and reattachment over local sockets.
@TroyMitchell911

Copy link
Copy Markdown
Collaborator

LGTM, can be merged.
Thanks.

@muggle-stack
muggle-stack merged commit 80b8c95 into master Sep 18, 2026
2 checks passed
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