Skip to content

fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories - #32101

Closed
brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/deps-dompurify-next-advisories
Closed

brennanb2025 wants to merge 2 commits into
mainfrom
brennanb2025/deps-dompurify-next-advisories

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 0 0 0 0
Prod 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​67 $\color{#cf222e}{\Huge{\mathbf{−}}}$​67 0

ELI5

A new security check (Dependency security, added in #31858) fails on every PR that touches a lockfile, because main itself still depends on versions of two libraries with known critical security holes: DOMPurify (the HTML sanitizer we run on Mermaid diagrams and notebook HTML output) and Next.js (used only by the docs site). This PR moves both to the first patched release, so the check passes again.

What Changed

Problem: the javascript job of the Dependency security workflow reports BLOCKING advisories on main's unchanged lockfiles, so every PR touching a package.json or lockfile goes red (example: https://github.com/stablyai/orca/actions/runs/39848315907 on #30644):

Change:

No user-facing behavior change is expected. The lockfile diffs contain only the bumped packages (local pnpm churn removed); no entries were added to config/dependency-security-exceptions.json.

Why

Both versions are the lowest patched release for every listed advisory, read from the GitHub advisory database: all three DOMPurify advisories are first patched in 3.4.17, and all three Next.js advisories are first patched in 16.4.1 on the 16.x line. Bumping the versions removes the findings instead of adding audit exceptions.

The listed DOMPurify advisories concern IN_PLACE, SAFE_FOR_TEMPLATES with shadow roots, and the SMIL attributeName guard in XHTML mode. Our direct string-input calls and inspected Mermaid callers enable none of those modes. However, 3.4.17 also changes common namespace, raw-text and attribute validation paths, so unused modes alone do not establish rendering compatibility. The Monaco override updates its resolved dependency edge; Monaco Markdown hovers still use its unchanged bundled sanitizer, outside this upgrade.

Plan (full review path): update both dependencies to the first patched versions on their current major lines, keep lockfiles limited to their graph changes, verify advisory records and package integrity, and exercise real desktop/mobile sanitization before readiness. DOMPurify is a patch upgrade; Next.js 16.3.8 -> 16.4.1 crosses a minor release.

  • Small: 67 added and 67 removed lines across six manifests/lockfiles; the five-file light criterion is not confirmed.
  • Risk areas: no saved-state, wire, lifecycle, auth or process code changes; the docs framework and platform compiler packages also change.
  • Repeat: dependency-security remediation touched these areas within the last 30 days, so the strict no-repeat light criterion fails.
  • Local effect: DOMPurify is shared across desktop and mobile rendering surfaces; this is broader than one component or pure function. Full review applies.

Decisions to raise early (one line each):

  • New user-facing capability or changed outcome: no new capability or layout; security upgrades retain existing rendering behavior, subject to real rendering QA.
  • Removes or worsens a flow older phone apps, paired servers or agent CLIs rely on, or orphans saved data: no, no wire, saved-data or API change.
  • Picks a new layout, control or interaction: no, no UI change.
  • Rests on a contradicted or conflicting ruling: no.

Linked Issue

Main fix for the Dependency security workflow added in #31858; supersedes the docs/site next bump tracked in #32094.

Visual Proof

Targeted before/after rendering evidence is required for the shared shipped sanitizer and will be linked in the review status comment. QA remains pending until that run completes.

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • node config/scripts/check-dependency-security.mjs (the script behind pnpm check:dependencies) locally: no BLOCKING findings in any lockfile.

  • With dompurify@3.4.17 installed: MermaidBlock.test.tsx, DiagramViewerModal.test.tsx, IpynbRendering.test.tsx, NativeChatMarkdown.mermaid.test.tsx: 4 files, 29 tests passed.

  • docs/site lockfile re-resolved with pnpm install --lockfile-only (pnpm 12.14.0) and it matches this diff except for local-only @pnpm/exe entries, which were dropped.

  • No new tests added for the version-only diff. The four existing test files passed 29/29 again on the reviewed head, but the Mermaid tests mock Mermaid/DOMPurify and the dialog tests consume supplied SVG; the notebook test exercises real HTML sanitization. These tests do not replace real desktop/mobile rendering QA.

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: closes six advisories. Cross-platform, SSH and mobile: no code change; mobile only moves to the patched DOMPurify inside the same mermaid range. Docs site: Next.js minor upgrade to the first patched 16.x release. The exact-head docs CI frozen install, tests, lint, typecheck and production build passed.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ity advisories

dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and
GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches
GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the
docs site.
@brennanb2025
brennanb2025 marked this pull request as ready for review October 22, 2026 20:57
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⛔ Files ignored due to path filters: 3
  • docs/site/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 835098c5-a945-406c-8bed-f14a2ce32e7d
📥 Commits

Reviewing files that changed from the base of the PR and between a74266c and c638c69.

⛔ Files ignored due to path filters: 3
  • docs/site/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 9497cdad-4612-4618-98df-7bab9ac32ea0
📥 Commits

Reviewing files that changed from the base of the PR and between e61d062 and a74266c.

⛔ Files ignored due to path filters: 3
  • docs/site/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • mobile/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing: 3
  • docs/site/package.json
  • package.json
  • pnpm-workspace.yaml

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.

📝 Walkthrough

The Next.js dependency in docs/site/package.json was upgraded from 16.3.8 to 16.4.1. The dompurify devDependency and the monaco-editor override for dompurify were both upgraded from 3.4.16 to 3.4.17.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to a7426

The dependency updates are limited to version declarations and introduce no identified behavioral or deployment risk.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Description check Warning The description follows the repository template, with ELI5, What Changed, Why, Linked Issue, Visual Proof, Testing, Review, Notes, and Checklist sections all present. It explains the Dependency securi… 1. Replace the blank 'Fixes #' line with a real linked issue number. Do not rely on 'Refs' or on other PRs. 2. Add targeted before/after rendering evidence in the Visual Proof section, or state the reason it is N/A. The author says this evi…
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies both dependency upgrades and their security purpose. It is specific and directly related to the main changes.
Docstring Coverage Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (3 skipped: 3 unsupported.)

Full details: Description check

Explanation

The description follows the repository template, with ELI5, What Changed, Why, Linked Issue, Visual Proof, Testing, Review, Notes, and Checklist sections all present. It explains the Dependency security workflow failure, the dompurify 3.4.16 to 3.4.17 and next 16.3.8 to 16.4.1 bumps, the advisories addressed, and the testing performed. Gaps remain. The Linked Issue section has no real issue number, only a reference to #31858 and #32094 with 'Fixes #' left blank, and the template requires an actual linked issue for outside contributors. The Visual Proof section gives no before/after evidence and defers it to a later comment, although the template requires it for behavior changes. The 'Automated tests' box is checked but no new tests were added, and the 'Agent skill upstream boundary' and 'AI Disclosure' sections are left empty. Some checklist items are marked done while QA is still pending.

Resolution

  1. Replace the blank 'Fixes #' line with a real linked issue number. Do not rely on 'Refs' or on other PRs. 2. Add targeted before/after rendering evidence in the Visual Proof section, or state the reason it is N/A. The author says this evidence is pending, so link it in the PR body when it exists. 3. Fill in the AI Disclosure section, or state that the contributor is a StablyAI team member and the section does not apply. 4. Re-check the Testing checkboxes so they match the work done. The 'Automated tests added/updated' item should explain that no tests were added and why. 5. Clear the Checklist items that are not yet true, such as the Visual Proof item while QA is pending.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

OrcaWin pushed a commit that referenced this pull request Oct 23, 2026
Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.
OrcaWin added a commit that referenced this pull request Oct 23, 2026
* test: remove redundant cases and repair behavioral assertions

* docs: reconcile audit records with current main

* fix(deps): apply security updates from #32101

Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.

* test: satisfy changed-line quality gates

Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
@brennanb2025

brennanb2025 commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Main’s locked DOMPurify and docs-site Next.js versions contain the reported security advisories, causing the dependency-security workflow to block unrelated pull requests that change lockfiles. This PR updates DOMPurify to 3.4.17 in the desktop and mobile dependency graphs and the docs site’s Next.js to 16.4.1, preserving the security gate. The intended user-facing result is unchanged legitimate HTML and Mermaid rendering with the upstream sanitizer fixes.

Not ready: actual mobile emulator QA remains unverified. Tooling diagnosed the four earlier lead stalls as Codex load-balancer capacity: a session was pinned to a full account. Those stalls do not count toward the limit for setup failures with an unknown cause. The mobile retry is queued through the QA gate with the short mobile-only brief and the bounded first step to set up and boot the emulator. A stalled lead will be replaced with a fresh session; after three consecutive stalls on one admission, the slot will be released and the retry requeued. No mobile pass or completion marker is claimed.

Two review rounds found no proven production P0, P1 or P2 issue. The PR description was corrected to identify the Next.js minor-version upgrade and the limits of mocked Mermaid tests. Main was merged once before final QA; the resulting six-file dependency patch is identical to the reviewed patch. Head: c638c69dab0c014d45dcd7908382df70a0c41d4d. The latest GitHub check confirms the PR is open and out of draft; all 66 registered checks succeeded or were intentionally skipped.

Verified on that head: all registered CI checks succeeded or were intentionally skipped, including dependency security, desktop checks, mobile bundles and the docs frozen-install/test/production-build job. Built desktop QA compared baseline 52e413ec with the head using identical fixtures: rich Markdown, separate preview, standalone Mermaid files, saved chat rendering, real PR descriptions/comments and expanded diagrams, notebook HTML, hostile-content removal, and the same notebook reopening after an actual upgrade. No new rendering or active-content regression was observed. Both rigs were stopped by their recorded identities; six real configuration hashes were unchanged.

Deferred, pre-existing: Monaco embeds DOMPurify 3.2.7; the package override does not replace that bundled runtime. No exploit was established. Desktop QA also observed notebook authored CSS/filter IDs being stripped and poor contrast in one neutral-theme comment diagram on both baseline and head. These are existing limitations, not claims of full style preservation or universal security coverage. Real-agent responses, provider login and other operating systems were outside this targeted rendering run. Mobile valid/growing/invalid/hostile Mermaid and reopening previous-build content still need the actual emulator run; passing mobile bundle CI does not replace it.

For Brennan’s review

The desktop pairs below show baseline on the left and this PR head on the right. No new product choice or deliberate rendering difference needs a ruling. Mobile screenshots are absent because that run did not execute.

Surface Main PR head
Markdown Mermaid preview Main Markdown Mermaid preview PR head Markdown Mermaid preview
Hostile and malformed Mermaid fallback Main Hostile and malformed Mermaid fallback PR head Hostile and malformed Mermaid fallback
Notebook HTML Main Notebook HTML PR head Notebook HTML
Expanded Mermaid Main Expanded Mermaid PR head Expanded Mermaid
Real PR description Main Real PR description PR head Real PR description
Real Conversation comment Main Real Conversation comment PR head Real Conversation comment

The saved notebook automatically reopened after upgrade; this view follows dismissal of the unrelated startup promo:

Restored notebook on PR head

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Closing: #32326 (merged 10-23) made the same fix on main. It bumps dompurify to 3.4.17 in the root, the mobile app and the override, and moves the docs site to next 16.4.2. That covers every file this PR changed. The Dependency security check is green on main again, so nothing is left for this PR to do.

@brennanb2025
brennanb2025 deleted the brennanb2025/deps-dompurify-next-advisories branch October 23, 2026 20:33
nwparker added a commit that referenced this pull request Oct 24, 2026
* fix(git): preserve unmerged branches with no net file changes

* fix(workspace): show launch actions in empty tab groups (#32182)

* fix(workspace): show launch actions in empty tab groups

* fix(workspace): resolve empty-pane browser policy by owner

* Scope daemon inventories for safe worktree teardown (#32171)

* Fix scoped daemon inventories without changing terminal input routes

* Serialize daemon owner inventory refreshes safely

* Serialize daemon owner inventory refreshes safely

* test(daemon): protect baseline inventory budgets and recovery

* fix(daemon): clear route proof during synthetic exits (#32196)

* docs: condense test anti-pattern guidance (#32201)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* refactor: colocate repository draft input and tests (#32155)

* test: consolidate discovery coverage and replace copied oracles (#32211)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: retire copied oracles and implementation inventories (#32206)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: remove classification inventories and verify settled boundaries (#32216)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* test: keep explicit regressions without legacy algorithm copies (#32218)

* test: keep explicit regressions without legacy algorithm copies

* test: use Number.NaN in latency fixtures

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* ci: avoid slow Ubuntu mirrors during WebKit setup (#32205)

* ci: reuse bounded Ubuntu mirror setup for WebKit

* ci: preserve Ubuntu setup for dispatched older refs

* perf(renderer): scope runtime connection and parking subscriptions (#32199)

* fix(chat): allow visuals to declare open without breaking scripts (#32226)

* fix(native-chat): keep queued messages in the chat's own record, so a refused one can't be skipped (#30418)

* fix(native-chat): a refused queued message still blocks the queue when its card write fails

Every reader of a queued card goes through one derived view of the journal's dispatch facts,
so a card whose message was refused reads as returned even when its card-table update and heal
both failed. One decision and one writer store the derived state for every action.

* refactor(native-chat): keep each queued message in the chat's journal, not a second table

A queued message lived in its own SQL table, and a hook that may be skipped copied
each refusal from the journal into it. When that write failed, a refused card
vanished: the cards behind it sent, and its Send and Delete did nothing.

Each queued message is now a `queue` row in the chat's journal beside the
submission that hands it off and the dispatch row that answers it. The reducer
folds the card from those rows, so a refusal returns the card and holds the
cards behind it with nothing written, and replay reads the same. Every action
writes its own rows in one transaction. Cards an earlier build stored are read
through the same classification until they move: each action moves its own
card, and a bulk import runs once per open. A new epoch restates every card
still owed an answer in the transaction that deletes the old rows.

The settlement hook and its savepoint, the open-time heal, the drain's heal and
the derived-card view are gone. The published card list is unchanged.

* test(native-chat): adapt the queue tests to cards kept in the journal

Cards are written through the journal, so tests queue them with their body
alone and hand them off under their real fingerprint. Tests of the removed
settlement hook, heal and SQL store are deleted; the text-update spec now
covers the edit decision alone, and a test of an earlier build's stored holds
seeds the legacy table and reads the imported cards. Rolling over an epoch now
keeps a card whose hand-off was never handed over, so that test checks the
retried id never sends rather than that nothing sends.

* test(native-chat): a refused card holds the queue through rewind, a new epoch and a restart

One whole-flow test through the host: the refusal writes no card row, a rewind
that retires the refused hand-off and a rolled-over epoch keep the card
returned, a restart and Resume send nothing behind it, its own Send refused
again still holds the queue, and its Delete releases the next card in order.

* test(native-chat): an earlier build's queued cards move into the journal once, each read the same

One mixed-import test through the host. The first open's import fails and
moves nothing, yet every card reads as it will; Delete and Send each move only
their own card. The next open moves the rest in one transaction, writing a
refusal only where its hand-off is gone, nothing for answered cards, and leaving
an unreadable row on disk. A later command turn spends a moved command card, a
third open writes nothing, an old Delete replays from its receipt, and a chat
with only answered legacy rows clears them in a delete-only transaction.

* test(native-chat): adapt typechecked tests to cards kept in the journal

The row-kind table lists a queue row, the clear and legacy-import tests
match the current signatures, and the downgrade test keeps only the
leftover-send case: a queued card is now a newer-version journal row an
older build keeps read-only, and users don't downgrade.

* refactor(native-chat): drop the journal row writer's unused transaction hook

No caller passes one since the queue's savepoint bookkeeping left.

* test(native-chat): downgrade probes write rows at the version older builds read

The highest version this build reads is now 4, the queue row's. A newer
build's row in these probes is written at version 3 so the older build
meets its kind, not a newer version, and the Stop-event probe hands the
older build the rows as stored, since a read raises each row's version.

* test(native-chat): unit tests follow the queue row's version 4

The legacy-import test opens real SQLite, so it runs in the Node runtime
project. The newest version this build reads is now 4, and a read raises a
row's version to it, so the turn-lifecycle test no longer pins the
in-memory version (the written one stays pinned by journalRowSchemaVersion).

* fix(native-chat): one batch echoing two refused queued messages spends both

The per-row delivery check returned only the first waiting card an echo
proved delivered, so when one settlement carried echoes for two cards the
second stayed waiting and was sent again. It now returns every card the
row proves delivered, and the fold withdraws each; replay folds the same.

* fix(native-chat): a rewind moves an earlier build's cards into the journal first

While the open's import had failed, an earlier build's card that read as
refused through its hand-off lost that reading when an in-place rewind
retired the hand-off: it disappeared and the card behind it sent. The
rewind now adopts every legacy card ahead of its tombstones and deletes
their rows in its own transaction, as an epoch replacement already does,
so the fold latches each card's reading before its hand-off is gone.

* fix(native-chat): keep an earlier build's card whose hand-off is still unconfirmed

The import dropped a legacy card handed off as a send whose delivery was
unknown, though the fold keeps such a card linked and a later refusal
returns it. The import now keeps every card whose hand-off a later row can
still answer, through the same check the reducer folds dispatch rows by.

* fix(native-chat): one delivery echo withdraws at most one queued card

One provider echo proves one delivery, yet it withdrew every waiting card
with the same text whose hand-off was refused, so a second identical card
was lost. The fold now pairs each echo with one card: on its own row the
earliest-positioned match no other echo of that row took, and for a card
that waits again the earliest applied echo no card has taken. Paired echoes
live in the fold's state, so live and replay pair the same; readers of
cards not yet imported work on a copy.

* fix(native-chat): a queued message handed to the agent is sent again only on proof it never got it

Pressing Stop after Orca handed a queued or steered message to the agent,
before the agent confirmed it, put the card back in the queue, and Resume
or the next send sent it again. A check matching the agent's echo of the
text was the only guard against a double send, and it kept trading a
resend for a lost card. Whether the card waited again was decided by the
rejection's category, not by proof the agent never got the message.

A refused hand-off now waits again only where Orca proves the agent never
received it: the host still held it, or whoever settled it recorded that.
A failure still returns the card and holds the cards behind it. Anything
else may have reached the agent, so the card is spent and its message
reads as sent, derived on every fold from the stored rejection: with the
turn it was answered into, as that turn's unconfirmed message where it was
handed over; withdrawn before any turn opened, as a person's own send a
Stop took back. The reopen mark ignores a recovered doubt, and the restart
reconciler no longer judges a queued hand-off unsent from what history
lacks. The echo text matching and its pairing are deleted.

A close or quit while the agent starts settles sends with the unrun hold.
The host hands nothing to a child before its start answers
(structured-agent-session-delivery-loop.ts: a handover needs phase
ready), so such a send went to an earlier child and may have run: it now
reads as sent instead of waiting again.

* fix(native-chat): Claude's report that it removed a queued message unrun lets the card wait again

Claude says when it removed a send without running it: the interrupt
receipt's cancelled list, or a command_lifecycle cancelled, discarded or
refused frame for a command that never started. Every one settles through
rejectClaudeDispatchWaiters, which now marks the settlement unrun. For a
queued card's hand-off the host records that as proof the agent never got
it, so the card waits again under the queue's pause. A direct send keeps
the plain refusal: there the proof would owe it as stopped history.

* fix(native-chat): an earlier build's card stopped after hand-over follows the same rule on upgrade

Earlier builds put a card back to waiting after any refusal without a
verdict and cleared its link, so a card the agent may already have would
be sent again after the upgrade. The card's own hand-offs in the journal
decide now: unchanged and last sent as one that may have reached the
agent, it is spent on that hand-off and its message reads as sent; edited
or sent again since, it is held for its own Send under a host-only marker
that publishes as a plain hold; with no hand-off in doubt, including one
that never left Orca, it waits as before. The import writes a card spent
on a hand-off in doubt, so its message shows.

* fix(native-chat): read a queued row's hand-off before the lookup that uses it

The lookup's callback lost the narrowing of the row's change, which the
mobile test typecheck rejects.

* test(native-chat): a card steered into a turn a Stop ends stays sent in the Stop tests

Three tests pinned the old rule, that a card handed into a running turn
comes back to waiting when a Stop ends it. Under the new rule nothing
proves the agent never got it, so the card is spent: with the turn named,
its message reads as that turn's unconfirmed one; without, as a send the
Stop took back. Neither is ever sent again.

* fix(native-chat): a queued message stopped before Orca handed it to the agent waits again

A Stop that aborts a hand-off after its row is written but before the agent call now records
the withdrawal as never reaching the agent, for queued hand-offs only, so plan D's rule returns
the card to waiting under the Stop's pause instead of spending it. Direct sends keep the plain
withdrawal (the flag would owe them as stopped history). The agent's own refusal while aborted
is kept rather than overwritten, and an abort thrown during the call stays in doubt.

* fix(native-chat): a queued message stopped while the agent prepares it waits again

The abort signal reaches only an adapter's preparation (its options, the previous turn opening),
never its write, so the signal's own reason thrown back is the adapter saying nothing was
written. The handover now reads that, for queued hand-offs only, as never reaching the agent,
so the card waits again under the Stop's pause. Claude no longer wraps that abort as an option
refusal, which had returned a stopped card as a refused one blocking the queue. A real refusal
keeps its cause and its barrier; any other throw stays in doubt and is never resent.

* test(native-chat): the usage-limit Continue backlog case writes its card through the journal queue

Main's #30396 wrote its backlog card with the stored fingerprint and host-instance stamp the
journal queue derives instead; the card the Continue must yield to is the same waiting card.

* test(claude): the real-CLI queued Stop expects the unrun mark Claude's cancelled-unrun receipt carries

* fix(native-chat): retain spent legacy messages when import fails

* Bound per-write chat selector work and guard ownership scans (#32109)

* Guard per-write chat selector work with shared ownership indexes

* Use checked sender session identities in the scale fixture

* Read each indexed tab row once during snapshot rebuilds

* Fix lexical binding and cache matching in selector lint rules

* Keep behavioral selector guards and resolve imported bindings

* Require selector binding definitions to declare functions

* Key performance subscribers by saved chat scope

* Show activity status with worktree-style counts (#26443)

* Show prioritized agent counts on the activity bell

* Move activity badge into the corner and shrink its count

* Use a fixed side column for activity status and counts

* Fix activity count spacing and status alignment

* Use worktree-style superscript counts for activity

* Keep activity count positions and update only number styling

* Keep idle layout fixtures out of active agent status updates

* Show child workspace statuses in the children toggle (#32224)

* Show child workspace status counts in lineage toggle

* Give child status summaries room and wrap narrow layouts

* Use solid working dots and keep child summaries on one line

* Restore children icon and clarify worktree tooltip

* Keep child totals clear of status counts in narrow layouts

* fix: preserve workspace review references when clearing active reviews (#32263)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* Saving a file, then closing that tab mid-save, no longer leaves its other tabs looking clean (STA-11786) (#32259)

* fix(editor): settle a save's shared record when the saving tab closed mid-write

With one file open in two workspaces, closing the saving tab while its write was in
flight left the other tab's disk baseline, dirty flag, draft and loaded text on the
pre-save version: every completion step looked the closed tab up by id and found
nothing, and the saved event listed only the closed tab as the record's views. An undo
back to the old text then read as clean and closed without asking.

The write now captures its record id with no await before the write, and completion
(rebaseline, dirty/draft settle, conflict clear or refusal banner, saved event's view
list) goes through a live view of that record, preferring a writable one. The save
runner and the write-lock wait do the same, so a save queued by a tab that closes
still writes through the record's remaining view. The chat visual's saved-event
listener also falls back to the record's views.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(editor): follow a closed saver's record through re-keys, kept records and listeners

Review follow-ups to the closed-saving-tab fix:

- Completion now finds the record through the view ids captured before the write,
  not the record id: a re-key mid-write (an unpinned runtime tab resolving its
  pairing) keeps views but changes the id, which brought the original bug back.
- A fallback writer (asking view closed while queued or waiting on the file lock) is
  chosen like autosave/restart's writer: writable and on a current connection, so a
  stale-connection sibling no longer throws when a current view exists.
- With no view of the record left, a kept record (no-prompt removal) is dropped when
  the write saved its text, else rebaselined; other open views of the file (a
  detached peer) take the written baseline, keeping their edits.
- A save may mark a record clean through a read-only or CSV-preview view (markFileDirty
  `written`); such views still never become dirty.
- Markdown preview refresh and the CSV pending-cell listener match the saved event's
  record views, not only the saving view's id.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(editor): name the closed-view save harness as a test fixture

The focus-read ratchet scans non-test sources; the `.test-fixture.ts` suffix marks this
harness as test-only like the repo's other fixtures.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(ci): list the closed-view save rig as a unit support bridge

The rig builds on the autosave test fixture, which takes the editor slice harness, so
the routing check saw it as a product consumer of that harness. Bridges are an explicit
list, like the browser drawn-from-main fixture.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>

* Show task names for Codex subagents (#32243)

* fix: name Codex subagents from rollout metadata

* Hide generic default role in child agent rows

* Reject non-regular Codex identity transcripts without blocking

* [comment audit] Shorten preload comments (#32257)

* chore: audit preload root comments and track repository coverage

* docs: track repository comment audit coverage

* chore: keep comment audit tracker local

* chore: shorten preload API contract comments

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix: offload macOS deletion spawns and reduce branch cleanup (#32227)

* fix: reduce worktree deletion branch cleanup overhead

Refresh only the remote base branches needed to prove a branch merged, with automatic maintenance disabled. Skip merge simulation when no common ancestor is available and preserve the branch when its remote base cannot be refreshed.

* fix: preserve fetch mappings and live branch cleanup fallbacks

* fix: spawn macOS worktree deletion commands off the main thread

* test: remove redundant cases and start comprehensive value tracker (#32269)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(web): the browser client advertises the desktop's chat capabilities, so its host opens chats (#32253)

* fix(web): the browser client advertises the desktop's chat capabilities, so its host opens chats

The browser client's handshake never named agent-session.structured.v1, so a paired
host refused every agentSession call with structured_agent_session_unsupported and
withheld chat tabs. The desktop window, the Electron remote handshake and the browser
client now build their lists from one shared array of what the renderer's chat implements.

Fixes #32145

* refactor(web): keep the browser client's capability list beside the desktop's in shared

The host-side test parses both handshakes with the host's own parser, which the node
typecheck project can only import from src/shared.

* test(web): assert close-intent support in the auth frame, not the whole capability list

* fix(terminal): a timed-out CLI split names its pending pane, and a retry can never re-run its command (#32278)

* fix(terminal): a timed-out CLI split names its pending pane and can be retried without splitting again

Fixes #32169

* fix(terminal): a blank retry id or a closed pane never re-runs a split's command

* refactor(terminal): a split retry only waits for its pane and never asks the window again

* fix(terminal): a split missing from the graph is reported pending, never closed

* fix(ssh): never block or over-read on an SSH identity file (#32274)

Every place Orca reads an SSH key or identity file now goes through one
reader that refuses directories, FIFOs and devices (including through
symlinks) with a nonblocking open, and caps the read at 1 MB. An auth
failure now names any identity file that was skipped and why.

Fixes #31965. Reported by @Huge, who also proposed the first fix in #31966.

Co-authored-by: Martin Vlach <mr.huge@seznam.cz>

* test: finish fixture work before unit-test cleanup (#32285)

* fix(mobile): keep a terminal tab's chat identity while its handle is unchanged (#32251)

* fix(mobile): keep a terminal tab's chat identity while its handle is unchanged

A status frame that omits agentType or providerSession flipped chat back to the
terminal for tabs with no launchAgent (#31977), and left a launched tab with no
session to read (#32134). Mobile now remembers the last resolved agent and
session per tab and handle, so a missing field is treated as missing evidence.

* fix(mobile): keep remembered chat read-only and drop it on relaunch

A chat resolved only from this phone's memory may sit on a bare shell, so its
writes stay locked until live status confirms the agent. A live agent frame
without a session no longer inherits the previous conversation, and the
latest remembered agent outranks an older launch hint.

* fix(mobile): gate native-chat image sends on a confirmed live agent

An image send from a chat only this phone remembers could paste into a bare
shell; it now shares the confirmed-agent gate with text sends.

* fix(mobile): type remembered chat identity structurally

Importing MobileSessionTab into the eligibility module pulled React Native
globals into the e2e program through nested-agent-idle-session-owner.spec.ts,
retyping setTimeout as number there.

* fix(terminal): never present a half-built synchronized-output frame on reveal or recovery (#32281)

Settled reveal and every other atlas recovery forced the current xterm buffer
onto the screen even while a TUI held DEC 2026 synchronized output. Codex
erases rows inside each held redraw, so a reveal mid-redraw showed a blank
pane for a frame (#32198).

Viewport presents now have one mode: they keep the last coherent canvas and
let xterm present when the TUI closes the frame, bounded by xterm's 1s
synchronized-output safety timeout. Structural replay keeps its retained frame
up until an open synchronized frame closes, bounded by its existing hold cap.

Co-authored-by: YEONGUK LEE <contact@abruption.dev>

* refactor(renderer): colocate task page pagination rules (#32297)

* perf(test): bundle Zod once per isolated Vitest invocation (#32298)

* Use incremental tsrs locally and update Bun to 1.4.4 (#32276)

* Use incremental tsrs for local typechecks

* Update pinned Bun to 1.4.4

* fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories

dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and
GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches
GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the
docs site.

* Prepare mobile bundle for smoke benchmarks

* Give native compiler benchmarks a larger ARM runner

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

* test: trim redundant coverage and fix vacuous assertions (#32305)

* test: remove redundant cases and repair behavioral assertions

* docs: reconcile audit records with current main

* fix(deps): apply security updates from #32101

Apply the six-file DOMPurify 3.4.17 and Next.js 16.4.1 patch from PR #32101 to resolve the dependency security gate for this test audit. Original patch by Brennan Benson.

* test: satisfy changed-line quality gates

Add required braces and replace filter indexing with find. Rerun both CI quality gates and the 27 affected relay tests; refresh audit hashes and record the corrections.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>

* fix(ci): retain ambiguous unit-test import consumers (#32366)

* test: exercise real boundaries and trim redundant cases (#32353)

* test: replace redundant checks with observed behavior

* docs: reconcile completed audit batches and upstream review

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(git): retain zero-net history with current merge-base guards

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Martin Vlach <mr.huge@seznam.cz>
Co-authored-by: YEONGUK LEE <contact@abruption.dev>
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