Skip to content

🤖 fix: bash-monitor wakes are a level, never a queued edge (silent turn end after task_await) - #4071

Open
ethanndickson wants to merge 28 commits into
mainfrom
agent-turns-4r9r
Open

🤖 fix: bash-monitor wakes are a level, never a queued edge (silent turn end after task_await)#4071
ethanndickson wants to merge 28 commits into
mainfrom
agent-turns-4r9r

Conversation

@ethanndickson

@ethanndickson ethanndickson commented Sep 3, 2026

Copy link
Copy Markdown
Member

Summary

Agent turns ended silently (finishReason: "tool-calls", no text) right after a task_await on a monitored background bash task completed. Bash-monitor wakes are now a level the owner workspace reads at every tool boundary instead of a queued tool-end message that snapshots that level, which removes the race by construction and deletes the queued-wake cancellation protocol that existed only to retract those snapshots.

Background

Reproduced from a real session: bash with run_in_background: true + monitor, two task_await calls, the second returned "completed", and the turn ended with nothing said.

Chain:

  1. monitor:match → reconciler → dispatchBashMonitorWake queued the wake with queueDispatchMode: "tool-end" while the owner streamed.
  2. streamManager.createStopWhenCondition saw hasQueuedMessages("tool-end") and cut the stream after the current step.
  3. The same step's task_await showed the matched lines (shownThroughOffset advanced) → next reconcile auto-consumed → abortDispatch() withdrew the queued wake.
  4. Stream end dequeued the withdrawn entry, cancelBeforeAcceptance started nothing → empty queue, stream gone → silent turn end.

#4029 made the durable wake state level-triggered, but mid-stream delivery still materialized an edge artifact. #4067 (skip withdrawn entries in hasQueuedMessages) does not close the window because withdrawal happens on the next reconcile (microtask + owner lock + disk reads) while the SDK evaluates the stop condition immediately after the tool result.

Implementation

Two invariants replace the mirrors that earlier rounds kept patching:

A stream that yields to the wake level takes out a continuation debt. The session is the debt's only owner; it records why the debt was taken (the delegated-turn correlation of the cut stream) and either redeems it (the wake row's own stream starts) or voids it (no wake turn can come). Settlement is told about a void; it never probes for the wake.

A wake handed to the owner is a lease on a signal set. A lease is released (owner did not send) or committed (owner's row durable). Commit acknowledges exactly the leased signals, by identity. At most one offered and one committed lease exist at a time.

  • LevelBashMonitorWakeReconciler.hasOutstandingWake(owner): unshown signals exist (same-process blocking reads are not outstanding). ReconcileState holds offered?: Lease / committed?: Lease; a new lease is handed out only when both slots are empty. isCurrent() is status !== "released"; withdrawal (forgetDispatchFor, consumeCurrent, defer, outputShown, discardProcess, full-history clear, dispose) releases only an offered lease. commit() never throws: a released lease still commits (its row is durable), it retires any replacement lease that was offered into the emptied slot (whatever else that replacement carried re-derives on the reconcile scheduled after commit), acknowledges inline, and on failure the next reconcileOnce retries state.committed first while collect() overlays the committed signals so the level stays low and nothing is derived twice.
  • Stream stop conditionhasQueuedMessageshasPendingToolEndInput() in StreamRequestConfig / turnRequestBuilder (the AI SDK StopCondition accepts PromiseLike<boolean>). A non-empty queue arbitrates alone (head === tool-end; re-read after the level read so a message queued meanwhile still arbitrates); the level cuts only over an empty queue, matching the dispatcher, so a turn-end head is never promoted. A true from the level is the cut: wakeContinuationDebt = { correlation } is taken in the same synchronous step as the decision. The level is read live rather than from the published mirror because task_await showing matched lines lowers it through an async reconcile — a mirror read would cut every such stream for lines the step just displayed and then void the debt as retracted.
  • Session transitions (AgentSession, each the only place its consequence is computed): sendMessage / resumeStream are thin public wrappers that raise wakeTurnInFlight synchronously for a wake before the first await (so onAccepted lowering the level can never observe "no wake turn") and settle it on return; stream-start whose request metadata is bash-monitor-wake redeems (a compaction stream's request is the compaction row, so it does not); settleWakeTurnInFlight() keeps the marker only while a turn is in progress or an auto-retry is armed for the wake's durable row, and runs on IDLE, send/resume return, retryActiveStream return, and auto-retry-abandoned; the level lowering or the marker dropping with a debt outstanding voids it as retracted; other input voids superseded once its row is durable (settleWakeDebtForAcceptedInput; a send refused earlier leaves the wake owed, and a correlated re-dispatch continues the debt silently); clearPendingFollowUpFromSummary voids abandoned before erasing the durable follow-up (the record is the correlation's only carrier; settlement is idempotent). getQueueCutCutter() reports { stage: "bash-monitor-wake" } iff a debt exists; hasBashMonitorWakeContinuation() = debt ∨ in flight. A void whose owner hook fails (handle store / waiter / cleanup I/O) is parked in unsettledWakeVoids and retried at the next debt transition and on a timer, so a deferred handle never waits for a restart. Dispose clears everything silently and lowers the mirror.
  • Dispatch only when idledispatchBashMonitorWake defers (re-arming the after-idle reconcile) while the owner has a pending turn or busy session; returns plain "deferred" when a stream exists without a busy session (stream-end/abort/error listeners already reconcile); otherwise sends a direct synthetic turn with requireIdle + admissionStale: () => !isCurrent() carrying bash-monitor-wake muxMetadata, so it is never queued behind a racing manual send and correlation inheritance from history is unchanged. A goal-sync failure on a durable wake row arms and schedules the in-session auto-retry before committing the lease. The send runs under the owner's workspace event lock (AgentTaskIntegration.withWorkspaceEventLock, outermost; workspace removal takes the history lock while holding it): the lock is FIFO with the stream-end handler of the stream that yielded, so a wake can never start — and redeem the debt — before that handler has read the debt to defer the delegated turn.
  • SettlementhasSameTurnContinuation is synchronous: it defers iff the event-time queueCutSnapshot.cutter is the wake and workspaceService.hasBashMonitorWakeContinuation(workspaceId) (a sync read of the session debt). The snapshot is captured at stream-end emission, before the per-workspace event lock, so a void landing in between would be lost if the handler trusted the cutter alone; a cleared debt yields retracted_wake evidence and settles the handle with the retracted-wake error. The void hook (onWorkspaceTurnContinuationVoided(correlation, reason)AgentTaskIntegration.settleVoidedWorkspaceTurnContinuation) runs under that same lock and settles only records the stream-end already deferred (deferredOnly for retracted/superseded; abandoned settles running records too), so exactly one of the two paths settles a record whichever side of the lock the void lands. A void says nothing about other continuations of the same turn: if a correlated report is queued or already streaming (hasPendingWorkspaceTurnContinuation / a correlated active stream the record has not deferred), the void leaves the record for that continuation's stream-end. A manual tool-end head owns its cut and settles the handle without consulting the debt.
  • Debt across admission, compaction and restart — a correlated continuation admitted after the cut assumes the debt (debt.assumed) until its stream starts, and an assumed correlation survives a compaction auto-retry (getCarriedWorkspaceTurnCorrelation unwraps the compaction request's follow-up). The session keeps a small stream-start ledger (hasCorrelatedStreamStartedAfter) so a continuation that already ended before the predecessor's stream-end handler ran still defers rather than settles; that check, plus "another correlated continuation is queued", is re-evaluated at the settlement commit point (settleWorkspaceTurn.abandonIf, under the settlement lock after the reread) rather than from a pre-lock snapshot. A retried void whose earlier attempt persisted the terminal row re-enters the idempotent terminal branch, which now also resumes the skipped phases (waiters, execution-state mirror, disposable cleanup while the workspace is still registered). The compaction-completion path never awaits the void hook (the owner's stream-end listener holds the workspace event lock while awaiting the compaction decision). After a restart, the reconciler recovers "already delivered" from the owner's last durable wake row — direct or carried by an on-send compaction request (getCarriedBashMonitorWake) — and acknowledges matching signals instead of re-dispatching them. The level, the settlement predicates (cutter, debt, busy/queued) and the after-idle waits all resolve through the live session including one still running startup recovery (getLiveSession), so a wake deferred on a busy recovery session waits for that session instead of re-deferring in a loop; a send refused at the admission gates after its on-send compaction row landed rolls that row back; a failed history read during delivery recovery fails the reconcile (retried) rather than being recorded as recovered; hasPendingToolEndInput falls back to the queue head when the level read fails; and a wake refused after its on-send compaction row could not be rolled back consumes its lease — only when the readback confirms the row remains (an unknown outcome leaves the lease released) — and arms the in-session resume so the row stays its reachable single carrier. Delivery recovery finds the last durable wake row with an early-stopping backward scan bounded at the oldest outstanding monitor's arm time (a row older than that cannot acknowledge it) — no fixed tail depth, so a row buried behind a long turn or a compaction boundary is still recognised, and no whole-transcript read on an owner's first wake; rows whose persisted records lack usable identities are skipped, as are RLM preserved-tail copies (re-appended after the boundary with their source timestamps, so they would otherwise trip the cutoff before the scan reaches the wake row). A reconcile pass withdraws an offered lease whose signals no longer all derive outstanding (a monitor-lost wake offered from a registry row mid-removal is retired once the row is gone; the cancel itself schedules no pass), and a skipped compaction follow-up settles the delegated turn it carried only after its erase is durable (readback on a failed rewrite).
  • Yield leversyncToolEndYieldRequested() computes queue head ∪ wake level, drives backgroundProcessManager.setMessageQueued, and fires onToolEndYieldRequested on every rising edge; WorkspaceService backgrounds foreground task_awaits from that hook alone.
  • Deleted: cancelBeforeAcceptance + cancelSignal/cancelState, cancelReasonBeforeAcceptance, isNextEntryBashMonitorWake, hasPendingBashMonitorWakeContinuation, materializeMcpPromptSnapshots's cancel signal; from the redesign, streamYieldedToBashMonitorWake, turnCarriesBashMonitorWake, DispatchState.accepted, the seam's async hasOutstandingBashMonitorWake probe and settleSupersededWorkspaceTurnContinuation / onWorkspaceTurnContinuationAbandoned (folded into the void hook).
Earlier iterations (rounds 1–8) and why they were replaced

Rounds 1–8 fixed individual Codex findings by adding a marker per window: a PREPARING flag, dispatching-entry metadata, a cut latch, an accepted bit on the reconciler slot, and a settlement probe that unioned them with an async level read. Each fix moved the race to the next hand-off. The redesign replaces the union with a single owned debt and a single lease lifecycle; the model doc comments on wakeContinuationDebt and Lease carry the transition tables.

Net product code ≈ −350 LoC against main.

Validation

  • Dispatch: the wake send waits behind the owner's queued stream-end handling (event lock).
  • End-to-end regression in workspaceService.test.ts ("the stream yields on the wake level, not on a queued snapshot of it"): real reconciler + mocked process state; hasPendingToolEndInput() is true while the match is unshown, false once shownThroughOffset covers it, a queued tool-end message still yields, and the cut leaves getQueueCutCutter() = bash-monitor-wake.
  • Reconciler: unchanged signals re-dispatch after a defer; one lease at a time; a failing acknowledgment keeps the committed lease consumed (level low, no second hand-out, not dropped by outputShown/discardProcess) and the next reconcile persists it; a lease withdrawn under the send still commits and retires its replacement, with only the original signals acknowledged and the unseen process re-derived once.
  • Session (queueDispatch): debt taken at the cut and voided superseded once other input's row is durable (a refused superseding send leaves it owed); a void whose settlement rejects is retried once with the original reason at the next level transition; a correlated re-dispatch continues it without settling; level lowering with no wake turn in flight voids retracted; wakeTurnInFlight is true from sendMessage's first synchronous step (while onAccepted lowers the mirror) until stream-start; a wake refused before its row is durable leaves the debt to the next dispatch; a post-durability goal-sync failure keeps the wake in flight while the auto-retry is armed and settles it when the retry is disabled; dispose lowers the mirror and lever.
  • Compaction: a wake consumed by on-send compaction stays in flight through the compaction stream until its follow-up streams; an abandoned wake follow-up voids abandoned before the history erase, and a failed settlement retains the follow-up for retry.
  • Admission/compaction/restart: a correlated turn admitted after the cut assumes the debt until its stream starts and survives a compaction retry; the stream-start ledger defers a predecessor's stream-end for a continuation that already ended; a void whose continuation appears only on the settlement-lock reread is abandoned; a void retried after a partially persisted settlement wakes the waiter and removes the disposable workspace exactly once; a restarted reconciler acknowledges a committed lease from the durable wake row (direct or compaction-carried) instead of re-dispatching; a transient startup-recovery session receives the level; a send refused after its on-send compaction row landed leaves no compaction-request row; a busy transient startup-recovery session defers the wake and the idle wait stays pending until that session goes idle; delivery recovery rejects (and is retried) when the history read fails; a level read that fails while a tool-end head was enqueued still yields.
  • Task/turn manager: stream-end with the wake cutter defers while a continuation is owed, settles for a manual tool-end head, and settles with the retracted-wake error when the debt was cleared before the handler; a retracted/superseded void is a no-op on a running record, settles it once the handler has deferred it, and leaves it alone while another correlated continuation is queued.
  • workspaceTurnManager.test.ts has 2 terminal recovery ... failures that fail identically on pristine main. The merge also carries a one-commit fix for main-inherited breakage surfaced by CI (getSessionDir on Partial<Config> after 🤖 refactor: split the Config god-class into five focused stores #4017, archivingWorkspaceIds missing from 🤖 feat: optional flat chat list for the sidebar with project badges #3994's sidebar mocks after 🤖 feat: show "Archiving..." from every archive entry point while the request is in flight #4068, and an MCP snapshot assertion on the cancelSignal argument this PR removes).

Risks

  • Wake latency: mid-stream wakes take one extra idle→reconcile hop after the stream cuts. The stream still cuts at the same tool boundary.
  • Stop-condition cost: hasOutstandingWake runs collect() at every tool boundary (registry list + watermark reads under the owner lock). Cheap in practice, but new I/O on the hot path.
  • Void classification: a superseded void racing the stream-end handler is settled by the handler as retracted_wake (same terminal state, different reason text). A wake send that fails before its stream with auto-retry disabled or exhausted voids retracted, so the parent settles instead of waiting for a startup resume; the durable row itself still self-heals through startup recovery.
  • UI: a pending wake no longer appears as a queued message during a stream; the reconciler-derived pending-wake indicator already covers it.

Generated with xum • Model: anthropic:claude-fable-5-1 • Thinking: high • Cost: $224.34

A monitored bash task matching mid-step queued a tool-end wake that cut the
stream, then task_await showed the lines and the wake was withdrawn, leaving
the turn to end silently with finishReason tool-calls. The stream now reads
the reconciler level (hasPendingToolEndInput) at each tool boundary and the
reconciler dispatches only when the owner is idle, so the queued-wake
cancellation protocol is deleted.
@mintlify

mintlify Bot commented Sep 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Mux 🟢 Ready View Preview Sep 3, 2026, 7:02 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 3, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T14:18:34.181868Z 1a71e62 Manual request
🔒 Security Review Completed 2026-09-04T14:14:21.369819Z 1a71e62 Manual request

Security findings

Advisory findings (4)

ℹ️ About Codex in GitHub

Your team has set up Codex to 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceTurnManager.ts Outdated
Comment thread src/node/services/bashMonitorWakeReconciler.ts
Comment thread src/node/services/agentSession.ts

@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 Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: e01c03f358

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/node/services/bashMonitorWakeReconciler.ts Outdated
…-off wakes on clear/dispose

Codex review on #4071:
- P1: a direct wake send lowers the reconciler level at row persistence, before
  its stream is observable. AgentSession now marks a bash-monitor-wake send in
  PREPARING (hasPendingBashMonitorWakeTurn) and WorkspaceService folds it into
  hasOutstandingBashMonitorWake so delegated-turn settlement still sees the
  continuation.
- P1: a full-history clear or disposal forgets the in-flight dispatch; the
  receiver checks dispatch.isCurrent() under the history lock before sending.
- P2: session dispose and reconciler dispose lower the mirrored tool-end yield
  flag so a re-created session does not inherit a stale early-return.
…e before send

Codex security P2 on #4071: monitor cancellation cleared the level but a
dispatch already in the receiver's hands was still sent. discardProcess
(pull-side, no abort signal) forgets such a dispatch so isCurrent() turns
false, and dispatchBashMonitorWake re-checks isCurrent() after awaiting the
continuation send options, right before sendMessage.

Copy link
Copy Markdown
Member Author

@codex review

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
Comment thread src/node/services/streamManager.ts
Comment thread src/node/services/agentSession.ts Outdated
…t latch

- hasPendingToolEndInput/yield flag: a non-empty queue arbitrates alone; the wake
  level only cuts over an empty queue (turn-end head is not promoted).
- dispatchBashMonitorWake sends with requireIdle + admissionStale=!isCurrent(), so a
  racing manual send makes the wake a skip (re-armed after idle) instead of a queued
  entry the level can no longer retract.
- AgentSession records a wake-caused cut (streamYieldedToBashMonitorWake) until the
  next admitted turn so delegated-turn settlement sees the cut even if the monitor is
  canceled before the stream-end handler runs.

Copy link
Copy Markdown
Member Author

@codex review

Round 2 addressed in 6b64c5f: queue-head arbitration (turn-end head is never promoted by the wake level), the wake send is requireIdle + carries an admissionStale probe bound to isCurrent() (never queued), and a wake-caused cut is latched on the session until the next admitted turn so delegated-turn settlement sees it even if the monitor is canceled first.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 6b64c5f31b

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 6b64c5f31b

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/bashMonitorWakeReconciler.ts Outdated
Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/workspaceTurnManager.ts Outdated
… wake cut as cutter stage, probe try/catch

- Reconciler.outputShown(owner, processId) forgets a handed-out dispatch whose
  signal covers that process, so a wake still resolving send options while a
  manual turn showed its lines fails isCurrent()/admissionStale instead of
  admitting stale output.
- hasPendingToolEndInput re-reads the queue head after the async level read;
  a message queued meanwhile arbitrates (turn-end head => no cut).
- The wake-cut latch is now cut attribution only: getQueueCutCutter reports
  { stage: "bash-monitor-wake" } and hasPendingBashMonitorWakeTurn no longer
  consults it. A wake retracted after the cut (monitor canceled) settles the
  delegated handle as interrupted with a wake-specific supersede reason
  instead of deferring until the waiter times out.
- hasSameTurnContinuation catches wake-probe failures and settles normally.

Copy link
Copy Markdown
Member Author

@codex review

Round 3 addressed in 48afcd8: shown-frontier invalidation of handed-off wakes (outputShown), post-await queue-head recheck in hasPendingToolEndInput, the wake cut is now a QueueCutCutter stage (attribution) rather than a continuation marker so a retracted wake settles the delegated handle instead of deferring, and the settlement wake probe is try/caught.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 48afcd8a46

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 48afcd8a46

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
Comment thread src/node/services/workspaceService.ts
Comment thread src/node/services/workspaceService.ts Outdated
…s wake compaction follow-up, gate background waits on effective yield flag

Copy link
Copy Markdown
Member Author

@codex review

Round 4 addressed in b84c699: isCurrent() frozen at acceptance (FJ4K), wake auto-compaction follow-up keeps requireIdle (FJ4N), foreground-wait backgrounding gated on the effective yield flag after queue-head arbitration (FJ4Q). Each has a targeted test; all three threads replied to and resolved.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: b84c699fa3

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/bashMonitorWakeReconciler.ts
Comment thread src/node/services/agentSession.ts
- AgentSession owns the tool-end yield edge (onToolEndYieldRequested);
  WorkspaceService backgrounds foreground waits from that hook instead of
  from enqueue/level-publish call sites (PRRT_kwDOPxxmWM6fGVw_).
- Reconciler: accept() never throws; acknowledgment is attempted inline
  and retried by every reconcile pass while the accepted dispatch stays
  consumed (overlay in collect) (PRRT_kwDOPxxmWM6fGVxB).
- Abandoned compaction follow-ups settle their delegated turn via
  onWorkspaceTurnContinuationAbandoned -> settleSupersededWorkspaceTurnContinuation
  (PRRT_kwDOPxxmWM6fGVxG).

Copy link
Copy Markdown
Member Author

@codex review

Round 5 (fba966d) addresses all three findings with one principle — the layer that computes a transition owns its consequences: AgentSession fires the tool-end yield edge (onToolEndYieldRequested), the reconciler's accept() never throws and the reconcile pass retries a failed acknowledgment while the accepted dispatch stays consumed, and discarding a compaction follow-up that carries a delegated-turn correlation settles that turn (onWorkspaceTurnContinuationAbandonedsettleSupersededWorkspaceTurnContinuation).

# Conflicts:
#	src/node/services/agentSession.continueMessageAgentId.test.ts

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceTurnManager.ts Outdated
Comment thread src/node/services/agentSession.ts
Comment thread src/node/services/agentSession.ts Outdated
…le-before-erase

- WorkspaceTurnManager.hasSameTurnContinuation consults the wake level only
  when the event-time cutter is the wake itself; a manual tool-end head owns
  the cut and settles the handle (PRRT_kwDOPxxmWM6fOH50).
- A goal-sync failure on a durable bash-monitor wake row arms the in-session
  auto-retry resume before consuming the signal and schedules it
  (PRRT_kwDOPxxmWM6fOH54).
- clearPendingFollowUpFromSummary settles the abandoned delegated turn before
  erasing the durable follow-up so a settlement failure stays retryable
  (PRRT_kwDOPxxmWM6fOH59).

Copy link
Copy Markdown
Member Author

@codex review

Round 6 (44c09f2): wake deferral in hasSameTurnContinuation is gated on the event-time cutter (stage === "bash-monitor-wake"); a goal-sync failure on a durable wake row arms and schedules the in-session auto-retry before consuming the signal; clearPendingFollowUpFromSummary settles the abandoned delegated turn before erasing the durable follow-up.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 44c09f287c

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…dicates through the live session (transient recovery included); a failed wake-row read fails the reconcile instead of counting as no row; recheck the queue head when the level read fails

Copy link
Copy Markdown
Member Author

@codex review

Round 16 pushed: cutter and sync session predicates resolve through the live session (transient recovery included); a failed durable wake-row read fails the reconcile rather than counting as "no row"; the failing-level-read path rechecks the queue head.

@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: 6594bca1b4

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts

@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 Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 6594bca1b4

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment thread src/node/services/agentSession.ts Outdated

Copy link
Copy Markdown
Member Author

@codex review

Round 17: idle waits (waitForIdle, waitForIdleAndNoQueuedMessages) now resolve through the live session lookup so a wake deferred on a busy startup-recovery session does not re-defer in a loop.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: eae9eefc08

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: eae9eefc08

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Copy link
Copy Markdown
Member Author

@codex review

Round 18: a refused wake whose on-send compaction row could not be rolled back now consumes its lease (onAccepted) so the durable row is its only carrier and the reconciler does not redeliver.

@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: 551ca90cd7

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/agentSession.ts Outdated
Comment thread src/node/services/agentSession.ts
…s, and arm the in-session resume when it does

Copy link
Copy Markdown
Member Author

@codex review

Round 19: the compaction-row rollback now reports deleted/remains/unknown; a refused wake consumes its lease only when the row verifiably remains, and then arms the in-session resume like other durable pre-stream failures.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts Outdated

Copy link
Copy Markdown
Member Author

@codex review

Round 20: delivery recovery scans history backward with early stop instead of a fixed 50-row tail, so a wake row buried behind a long turn or a compaction boundary is still found.

@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: 91c35e31eb

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts Outdated
Comment thread src/node/services/workspaceService.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 91c35e31eb

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

… arm time and skip rows without usable identities

Copy link
Copy Markdown
Member Author

@codex review

Round 21: the delivered-wake-row scan stops at the oldest outstanding monitor's arm time (a durable exclusion point passed by the reconciler) instead of reading the whole transcript, and skips wake rows whose persisted records lack usable identities.

@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: 4f5a05df2f

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/workspaceService.ts
@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 4f5a05df2f

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Keep-recent compaction re-appends copies of the pre-boundary tail after
the boundary with their source timestamps, so an old-stamped copy at the
tail tripped the notBefore cutoff before the scan reached the wake row,
and a restart after a failed acknowledgment redelivered the wake. Copies
carry nothing their originals lack; skip them before the cutoff and
before reading wake metadata.

Made-with: Xum

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: a11a718fe9

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

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

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/node/services/bashMonitorWakeReconciler.ts Outdated
Comment thread src/node/services/agentSession.ts
Comment thread src/node/services/agentSession.ts
…ble erase

Reconciler: an offer is a claim on signals that derive now. A pass that
ran between a monitor's cancel and its registry-row removal offered a
monitor-lost wake for the canceled monitor and kept it current after the
row was gone; the pass now withdraws an offer whose signals no longer all
derive outstanding, and discardProcess no longer schedules the mid-removal
pass itself (the caller schedules after the removal).

AgentSession: clearPendingFollowUpFromSummary settled the delegated turn
before the follow-up erase was durable, so a failed rewrite left a live,
dispatchable follow-up under an interrupted handle. The void now follows a
successful rewrite, with a readback that recognises a rewrite that landed
but reported failure.

Pins the empty-queue drain keeping the wake's tool-end yield flag up.

Made-with: Xum

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 1a71e62fc1

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@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: 1a71e62fc1

ℹ️ About Codex in GitHub

Your team has set up Codex to 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 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +6961 to +6963
if (this.wakeTurnInFlight && !carriesBashMonitorWake(retryMetadata)) {
this.wakeTurnInFlight = false;
}

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 Preserve durable wake continuations after retry abandonment

When a delegated stream yielded to this wake and sendMessageInner() persisted its wake row but failed before starting the stream, disabling or exhausting auto-retry makes hasPendingAutoRetry() false here, so the in-flight marker is cleared and the already-lowered wake level voids the continuation debt. The durable row is explicitly retained for startup recovery, but this premature void can interrupt the parent handle and delete a disposable workspace before that recovery runs. Keep the continuation alive while a durable wake row remains recoverable, even when no in-session retry is pending.

AGENTS.md reference: AGENTS.md:L112-L112

Useful? React with 👍 / 👎.

Comment on lines +2220 to +2222
if (params.abandonIf?.() === true) {
return null;
}

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 Synchronize continuation admission through the terminal write

When a correlated report has already obtained the active turn metadata but is still in WorkspaceService.sendMessage() preflight, this guard cannot see it; fresh evidence after the earlier commit-point fix is that the guard remains a one-shot synchronous check before the awaited upsertWorkspaceTurn(), during which that send can become queued. The settlement can therefore persist an interruption and delete a disposable workspace underneath a valid continuation. Hold a shared admission/settlement reservation through the terminal write, or revalidate under synchronization that also covers preflight sends.

AGENTS.md reference: AGENTS.md:L150-L150

Useful? React with 👍 / 👎.

Comment on lines +7046 to +7047
if (this.disposed || this.shuttingDown) return;
this.unsettledWakeVoids.push(voided);

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 Retain failed void settlements across transient disposal

When startup recovery discards a durable compaction follow-up, clearPendingFollowUpFromSummary() launches this owner-side settlement without awaiting it; recovery can therefore finish, decide the transient session has no stream or retry to retain, and dispose it before the settlement rejects. This branch then drops the failed settlement because disposed is true, while dispose() also clears the parked voids and timer; since the durable follow-up was already erased, no later correlated stream can settle the delegated handle and its waiter remains blocked until timeout. Track in-flight void settlements in shouldRetainAfterStartupRecovery() or move their retry ownership outside the disposable session.

AGENTS.md reference: AGENTS.md:L150-L150

Useful? React with 👍 / 👎.

Comment on lines +647 to +649
const outstandingKeys = new Set(collected.signals.map((signal) => signal.key));
if (!state.offered.signals.every((signal) => outstandingKeys.has(signal.key))) {
this.releaseOffered(state);

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 Withdraw offers when the signal version changes

When an offered match changes to a terminal or runtime-lost signal before the owner accepts it, the process incarnation retains the same signal.key, so this key-only check leaves the obsolete offer current. Fresh evidence after the cancellation fix is that the replaced implementation compared a signature containing the kind, match offset, terminal timestamp, and shown state, whereas this check ignores all of them; the old prompt can therefore start a stale agent turn before the updated failure or settlement is delivered separately. Compare the offered signal versions, not just their process keys, when deciding whether the lease still derives.

Useful? React with 👍 / 👎.

Comment on lines +832 to +834
const recovered = signals.filter((signal) =>
deliveredKeys.has(signal.processId + "\u0000" + wakeUpdatedAtOf(signal))
);

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 Recover match and terminal wake acknowledgments independently

When a durable match wake's watermark write fails and that process then exits or becomes lost before restart, the current signal's identity changes: wakeUpdatedAtOf() now prefers the terminal settledAt or lost failedAt, while the durable row records the earlier createdAt:matchOffset. This equality check consequently fails to recover the already-delivered match, and reconciliation repeats its output alongside the genuinely new terminal/lost notification. Apply the delivered row's match frontier independently of the current terminal/lost version so restart recovery does not duplicate acknowledged output.

AGENTS.md reference: AGENTS.md:L112-L112

Useful? React with 👍 / 👎.

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.

1 participant