Repository navigation
fix: recover stale chat sends and drain one-shot CLI exits - #354
Merged
Merged
Conversation
devSejung
marked this pull request as ready for review
October 22, 2026 04:51
devSejung
force-pushed
the
fix/chat-history-send-sync
branch
from
October 22, 2026 06:47
1576adc to
aadc3dd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What Problem This Solves
Fixes false “The thread switched branches — review and resend” failures when Control UI sends a follow-up after the preceding history refresh failed. Related: #210, which covered history still in flight and same-transcript ancestor admission.
Also fixes the CLI exit lifecycle exposed by the first CI run: the dotenv/import-error child completed output callbacks and entered
process.reallyExit, then remained alive until its 240-second deadlock guard.Why This Change Was Made
The pane's history-request lifecycle now records an owned failed full-history read. Foreground send recovers through the existing history loader and checks connection, selected session/agent, and transcript identity after awaiting it. Failed recovery preserves the draft and attachments. Contextual actions retain their exact-leaf constraint. Gateway production admission is unchanged; copied stale leaves and empty pre-switch cursors remain rejected.
CLI one-shot finalization previously treated empty stream-write callbacks as permission to force native shutdown. Those callbacks do not establish that background native work has drained. The finalizer now records the exit code, restores terminal state, and lets Node drain naturally. It removes the synthetic stream-write/pending-stream path and preserves the existing five-second fallback for retained handles, using the latest exit code. Commands retaining handles may therefore take up to five seconds to terminate. Long-running commands without a one-shot completion request keep their existing lifecycle.
Production delta: +52/-25, net +27 lines; tests: net +212 lines. The positive production delta belongs to recording failed history reconciliation at its owner and fencing submissions across recovery; the CLI repair removes 13 net production lines. No new protocol, configuration, schema, dependency, V8 runtime flag, test retry, or increased timeout.
User Impact
A follow-up refreshes history before send instead of failing with a false branch-switch error. If history remains unavailable, the user sees the error and keeps the composed message and attachments. Navigation, reconnection, agent selection, or transcript replacement during recovery cannot send the draft into a replacement context.
Completed one-shot commands preserve output, terminal restoration, and exit codes while allowing pending native work to finish before shutdown.
Evidence
AI-assisted. Validated in local Docker Desktop Linux; independent autoreview found no actionable P0–P2 findings. Rebased onto
mainat5109dcad941e461593b6a64b5588bcab029b66de.Final hosted verification on
aadc3dd8d130d4780eea1bd7e268ab3f5ca6eb83: 65 checks passed, 11 skipped, zero failures or pending checks. OpenClaw CI run passed, including the previously failing checks-node-compact-small-6, all four browser E2E shards, lint, production/test types, and Docker smoke (separate workflow).expectedLeafEntryId: null) and passes after (first-response). Focused Gateway protection cases: 3 passed.WorkerThreadsTaskRunner::Shutdown/DefaultProcessExitHandlerInternal, with a V8 worker waiting inCollectionBarrier::AwaitCollectionBackground/StressConcurrentAllocationTask. The same fixture after repair finishes background work and exits with code 7 without invoking forced exit. The regression's stress flag is fault injection only.src/api/environment.ccat v24.22.0, andsrc/node_platform.cc. Relevant upstream native defect: nodejs/node#64274, pending fix #66171.platformclaw-check --changed --base origin/main --head HEAD --overlay-onlypassed without quick mode. Shared checks run in hosted OpenClaw CI./appsource checkout; existing npm-selection and persistence assertions are unchanged.Company VM deployment/live reproduction and native macOS execution were unavailable. Linux/browser evidence does not claim either. The retained-handle fallback still uses Node's native forced-exit path; this repair does not claim to solve every upstream Node shutdown defect.
Named follow-up: Audit reset lifecycle fencing for non-null ancestor admission (
src/gateway/session-reset-service.ts:1434,src/config/sessions/session-accessor.sqlite-active-events.ts:208). Reset preserves transcript identity; this PR preserves the existing Gateway contract.