Skip to content

🍱 fix: Carry Coding Approval Mode With Queued Turns - #16326

Merged
danny-avila merged 4 commits into
devfrom
lia/code-mode-continuity
Sep 25, 2026
Merged

danny-avila merged 4 commits into
devfrom
lia/code-mode-continuity

Conversation

@lia-by-librechat

@lia-by-librechat lia-by-librechat Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Pull Request

Summary

Queued Agent turns retain text, files, quotes, and selected skills, but previously lost the conversation's selected coding approval mode. When a queued message starts as a fresh ordinary turn, Full Access or Accept Edits can silently fall back to Ask. This is separate from the background-result return path fixed in #16322.

Capture the policy-filtered selection with each queued message and keep it through the durable queue, idempotent replay, and trusted continuation preparation. At execution, the existing live BYOM admission checks still decide whether the requested mode is permitted. An old client or existing row with no snapshot retains the old default, and changing an existing request ID's mode is a conflict rather than an implicit permission upgrade.

This PR addresses server-owned queued composer turns, including choosing Queue instead of a warm steer. A degraded steer that becomes a local follow-up still uses the existing ordinary-send path. HITL pause/resume already pins the original turn's mode, and a warm steer stays within its current run; neither path gains new authority here. This does not make the per-tool Approve action grant continuation-wide Full Access.

How it works

Composer's policy-filtered mode
  -> enqueue payload and durable row (optional on legacy records)
  -> queue fingerprint and exact replay
  -> claimed turn's trusted continuation preparation
  -> existing live run admission and HITL policy

No additional conversation lookup or model invocation is introduced. A mode named in an event payload cannot replace the claimed turn's selection.

Type of change

  • Bug fix

Testing

  • Current head 66cc995b08f08cf85682bbd0ead54803872b3de5: 865 focused tests passed across 23 suites, including the previously failing backend delivery suite.
  • npx tsc --noEmit passed in all four affected TypeScript workspaces. Shared package builds passed.
  • Touched-file formatting/import sorting and staged static checks passed.
  • Lighthouse was attempted. Mongo and the test server started, but Chromium could not launch because libatk-1.0.so.0 is missing on the host. No page audits ran.
  • No live BYOM browser approval-flow test ran.

Review Notes

The queued mode is a selection, not an approval grant. Client and HTTP schema validate allowed values, persistence and replay bind it to one queued intent, only an owner-scoped claimed row supplies the mode to the trigger host, and BYOM checks still revalidate against current settings on the new run. Omitting it on old records preserves legacy behavior. Existing pause/resume and background-completion tests remain part of the focused follow-up check.

Codegraph orientation matched skill 2026-09-24.4; the starting dev graph reflected 56301c1b5a32f37e5c59e09f0c8567f559ee0b76 before #16322 merged. Codegraph HUD.

Title: 🍱 represents a queued turn carrying its selected mode alongside its other contents. It had 1 indexed LibreChat commit use and 1 subject-leading use in 5,540 commits through 2026-09-23 23:08:36; the sentinel passed. The older use was about a compaction index, not queued turns. This nightly count is an as-of floor; the applied PR title is the spend record.

Review-Fix Update

Both original findings and all four subsequent Codex findings have fixes and replies. Protocol selection lives in the TypeScript backend. Direct versioned POST admission replaces the capability-list preflight, preserving structured priority responses and allowing idempotent receipt recovery after mutable access changes. Snapshot-bearing deliveries still require v2 workers and an atomic versioned publication reservation.

The approved dev synchronization is complete at 66cc995b08f08cf85682bbd0ead54803872b3de5. Both import additions were retained, the maintainer's rebase is preserved, and no force-push was used. All CI checks are green on this exact head, including every backend and frontend test shard. The prior conflict blocker is resolved; exact-head external re-review is still pending.

@lia-by-librechat

lia-by-librechat Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor Author

Review handoff for exact pushed head d4a1ea28be7aa145e021ec39575f35acada40c18.

Scope: server-owned queued composer turns now snapshot the policy-filtered coding mode at enqueue, persist it with the queue fingerprint and replay checks, and forward the owner-scoped claimed value through trusted continuation preparation. Existing live BYOM policy still validates at run admission. Preexisting queue records and clients without a mode retain their old default. This follows merged #16322 without reopening its background-return scope; there is no continuation-wide Approve grant here.

Checks on this exact head: 492 focused tests across 12 suites passed, including the merged background-return regressions, real queued-turn Mongo storage, client queue submission, and live policy tests. All four changed TypeScript workspaces passed npx tsc --noEmit; all four package builds passed before rebase; staged static checks passed. Lighthouse was attempted, but local Playwright Chromium is absent after the server started; CI runs its own lane.

Subsystem self-review covered event-payload isolation, queue ownership and tenant boundaries, persisted record projections, same-id replay conflicts, legacy rows, retry and claim/release paths, policy-fenced admissions, and pause/warm-steer behavior. Please review this SHA.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 25, 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-25T03:24:58.321619Z 9f06ac9 Manual request
ℹ️ 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: d4a1ea28be

ℹ️ 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".

item.manualSkills.length > 0 && {
manualSkills: item.manualSkills,
}),
...(codeApprovalMode != null && { codeApprovalMode }),

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 Gate approval snapshots on a versioned queue capability

During a rolling deployment with any pre-change queue replica, this field can be silently discarded: the old Zod enqueue schema strips the unknown property, and an old worker's record projection also omits it, while the advertised capability remains only { supported, durability }. A queued fullAccess/acceptEdits turn can therefore be accepted but execute as Ask; if its first response is lost and the retry reaches a new replica, the new exact-intent comparison instead returns an idempotency conflict because the stored row has no mode. Advertise and require a queue protocol/capability version before sending the snapshot so mixed replicas preserve deterministic behavior.

AGENTS.md reference: AGENTS.md:L42-L45

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 9f06ac905c23e56ce8f53a086a54179790e6ef68. Approval-bearing requests negotiate protocol v2 and use a versioned enqueue URL, so an older replica cannot strip the snapshot and accept it as v1. New deliveries require agent_queued_turn_v2 workers. Their v2 delivery key is reserved atomically with the queue row, preventing a crash between insertion and publication from allowing a legacy recovery worker to publish a v1 delivery. Regressions cover legacy endpoints, replica changes, lost-response retry, atomic reservation conflicts, and capable-worker lease recovery. Snapshot-free legacy requests retain their old path.

Comment thread client/src/hooks/Chat/useSteering.ts Outdated
const setDefaultAction = useSetRecoilState(store.duringRunDefaultAction);
const steerInterruptsByDefault = useRecoilValue(store.steerInterruptsByDefault);

const { selected: codeApprovalMode } = useCodeApprovalMode(conversation);

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 Use the combined-agent policy for queued approval snapshots

When an added conversation is active, the visible CodeApprovalMenu and ordinary-send path call useCodeApprovalMode(conversation, addedConvo), but this new queue path evaluates only the primary conversation. If the conversation retains fullAccess while the added agent's environment disallows it or its metadata is incomplete, the menu visibly falls back to Ask while queuing still snapshots fullAccess; admission can then either reject the turn or run the primary agent unattended contrary to the displayed selection. Pass the host's already policy-filtered combined selection, or at least addedConvo, into this hook.

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

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 9f06ac905c23e56ce8f53a086a54179790e6ef68. ChatForm now passes the active added conversation to useSteering, which evaluates useCodeApprovalMode(conversation, addedConversation) just like the visible approval menu. Queue snapshots therefore use the combined-agent selection, not the primary-only preference. Tests cover the queue handoff plus real policy evaluation with restricted and incomplete added-agent metadata.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Review fixes pushed and verified at 9f06ac905c23e56ce8f53a086a54179790e6ef68.

Both Codex findings have code fixes and replies in their original threads. This head uses the combined-agent selection for queued snapshots and fences snapshot-bearing requests and deliveries with protocol v2, including the insertion-before-publication crash window. Existing snapshot-free requests remain compatible. Live BYOM admission still validates the mode; this is not a continuation-wide permission grant.

Verification: 418 focused tests across 11 suites passed (API 51, client 189, real Mongo queue/delivery 149, transport/schema 18, routes 11). All four changed TypeScript workspaces passed npx tsc --noEmit. Staged static checks, touched import sorting, formatting, and commit hooks passed. The final lint-only fixture change was followed by a fresh client regression run and typecheck.

Lighthouse was rerun with worktree-local Chromium. Mongo and the test server started, but Chromium could not launch because the host lacks libatk-1.0.so.0; no page audits ran. No live BYOM browser approval-flow test was run.

Subsystem self-review covered combined-agent writers/readers, owner/tenant authorization, mode validation, queue replay conflicts, atomic publication identity, old/new replica compatibility, claim/lease recovery, and legacy rows. CI and a new exact-head external review are still pending. A maintainer must trigger Codex; the Lia GitHub App cannot trigger it.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review the latest 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: 9f06ac905c

ℹ️ 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".

const send = (res, result) => res.status(result.status).json(result.body);

const AgentQueuedTurnEnqueueController = async (req, res) => {
const enqueueController = (protocolVersion) => async (req, res) => {

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 Move the enqueue protocol factory into packages/api

enqueueController adds a behavior-selecting helper to the legacy CJS controller, making /api responsible for choosing and injecting the queue protocol version. Export version-specific handlers or a factory from packages/api instead, leaving this file with only requires, request plumbing, and calls into the TypeScript module.

AGENTS.md reference: AGENTS.md:L64-L68

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 47a6478d27d296f14016c11b356bc7e710a7c957.

Moved protocol selection and enqueue error handling into createAgentQueuedTurnEnqueueHandlers in packages/api/src/agents/queuedTurnHttp.ts. The CJS file now only supplies the existing dependencies and exports the returned handlers. Regressions exercise the real handler through both adapters, pinned protocol selection, owner-scoped receipt replay, and bounded error handling.

Comment on lines +1232 to +1233
const status = (error as { response?: { status?: number } })?.response?.status;
if (status === 404 || status === 501) throw unsupported();

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 structured 501 enqueue responses

When an approval-enabled user chooses Interrupt & Send, the v2 POST includes priority: true, so the enqueue handler returns 501 QUEUED_TURN_PRIORITY_UNSUPPORTED. This catch rewrites that response to 409 QUEUED_TURN_PROTOCOL_REQUIRED; consequently isDefiniteQueuedTurnsUnsupported no longer recognizes the priority response and useSteering leaves the message rejected instead of downgrading it to the local queue after aborting the active run. Only translate unstructured old-replica responses, and preserve recognized 501 codes.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 47a6478d27d296f14016c11b356bc7e710a7c957.

The transport now preserves structured origin responses, including 501 QUEUED_TURN_PRIORITY_UNSUPPORTED, instead of relabeling them as missing protocol support. Only unstructured 404/501 responses from an unavailable v2 endpoint become QUEUED_TURN_PROTOCOL_REQUIRED. Transport tests cover both cases, and the existing client priority-fallback regressions pass.

response: { status: 409, data: { code: 'QUEUED_TURN_PROTOCOL_REQUIRED' } },
});
try {
const { capability } = await listAgentQueuedTurns(payload.conversationId);

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 Distinguish preflight failures from ambiguous enqueue failures

If the capability GET times out, returns 5xx, or yields malformed data, execution never reaches the mutating POST, but the shared catch rethrows the raw error. useSteering interprets such non-4xx errors as an outcome-ambiguous enqueue, polls for a receipt that cannot exist for 60 seconds, and then leaves the message requiring manual recovery. Handle negotiation failures separately as definite preflight failures (or retry the read), reserving the ambiguous state for errors after the POST begins.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 47a6478d27d296f14016c11b356bc7e710a7c957.

Removed the capability-list preflight entirely. The versioned POST is the capability gate, so no pre-POST timeout, 5xx, or malformed list response can create an ambiguous enqueue state. Tests assert there is no GET dependency. Real POST transport failures retain their original uncertainty semantics.

response: { status: 409, data: { code: 'QUEUED_TURN_PROTOCOL_REQUIRED' } },
});
try {
const { capability } = await listAgentQueuedTurns(payload.conversationId);

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 Let idempotent retries reach the versioned POST

When an earlier v2 POST commits but its response is lost, a retry needs to reach the enqueue handler, which deliberately looks up the existing receipt before mutable conversation and agent checks. This unconditional capability GET instead calls the list handler, which authorizes the current conversation before reading receipts; if the conversation is deleted, its agent changes, or access is revoked between attempts, the GET returns 404/403/501 and the retry never reaches that replay path. The accepted turn may therefore execute while the caller sees a rejection, so negotiate without gating retries or make the preflight aware of the existing request identity.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 47a6478d27d296f14016c11b356bc7e710a7c957.

Retries now go straight to the same versioned POST with the same client request ID and approval snapshot. No mutable list/access check runs first. The lost-response regression revokes list access before retrying and still recovers the committed receipt. Backend tests continue to verify owner-scoped receipt lookup before mutable checks.

@danny-avila
danny-avila force-pushed the lia/code-mode-continuity branch from 9f06ac9 to 7ee9085 Compare September 25, 2026 03:28
@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Review handoff for exact pushed head 47a6478d27d296f14016c11b356bc7e710a7c957, a normal fast-forward push on top of the maintainer's rebase at 7ee9085c7fd23269b2b754f7bf413d74380e854e.

Backend CI failed in src/agents/triggers/service.delivery.spec.ts because two assertions omitted agent_queued_turn_v2. Those assertions are corrected and the suite passes.

All four new Codex findings are addressed, with replies in their original threads:

  • Protocol selection and enqueue error handling now live in TypeScript. CJS only wires dependencies.
  • Structured origin errors, including the 501 priority-fallback response, are preserved.
  • Direct v2 POST replaces the capability preflight, eliminating ambiguous states caused by a failed preflight.
  • Lost-response retries reach owner-scoped receipt lookup with the same identity and snapshot even if mutable list/access checks later fail.

Local checks: 659 focused tests passed across 17 suites (API 268, transport/schema 21, client 189, legacy routes 11, real Mongo queue/delivery 170). All four TypeScript workspace typechecks and shared package builds passed. Staged lint, formatting, import ordering, and circular-dependency checks passed.

Lighthouse was attempted again. Mongo and the test server started, but Chromium cannot launch without host library libatk-1.0.so.0; no page audits ran. No live BYOM browser approval-flow test ran.

Subsystem self-review covered alternate producers/readers, combined-agent policy, owner/tenant boundaries, receipt-first retries, definite versus uncertain transport failures, priority fallback, atomic publication identity, leases, mixed-version workers, and legacy rows. No new permission grant or persistence format is introduced by this closeout patch.

A review of this exact head is still pending. A maintainer must trigger Codex; the Lia GitHub App cannot trigger it.

@lia-by-librechat

lia-by-librechat Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor Author

Exact-head review handoff: 66cc995b08f08cf85682bbd0ead54803872b3de5. This supersedes the prior handoff after the maintainer-approved merge of dev (fe8a993997c0a8854faf8c4d0e555b919535f1de). The maintainer's rebase remains in history; no force-push was used. The sole conflict was resolved by retaining both the approval-mode import and dev's recovery imports.

All four new Codex findings remain addressed by ancestor 47a6478d27d296f14016c11b356bc7e710a7c957, with replies in their threads. The previously failing backend delivery-capability suite passes.

Verification on the merged tree: 865 focused tests across 23 suites passed (API 292, client/recovery 371, transport/schema 21, route adapters 11, Mongo queue/delivery 170). All four affected workspace typechecks passed. Staged static checks and the API rebuild passed. Redis recovery integration tests passed using a disposable worktree-local Redis process, stopped after the test run; no machine services were changed.

Subsystem re-review confirmed the combined-agent selection, snapshot transport, receipt replay, structured priority fallback, v2 worker fences, and dev's recovery safeguards remain intact. No new per-tool or continuation-wide permission grant was introduced.

CI and external review must cover this exact SHA. A maintainer must trigger Codex; the Lia GitHub App cannot trigger it. Lighthouse remains locally blocked by missing Chromium host libraries; no live BYOM browser approval-flow test ran.

CI update: Backend workflow 36095027777 completed successfully on 66cc995b08f08cf85682bbd0ead54803872b3de5, including the previously failing API shard. Only the frontend test shards were still running at the last check; no CI failures were reported. No external review has arrived for this head.

@danny-avila
danny-avila merged commit 163c1ad into dev Sep 25, 2026
36 checks passed
@danny-avila
danny-avila deleted the lia/code-mode-continuity branch September 25, 2026 11:28
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.

2 participants