Repository navigation
fix(git): preserve unmerged branches with no net file changes - #32181
Conversation
📝 Walkthrough
Priority: ➖ Normal Merge Risk: 🔵 Low · up to
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ 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:
db630c6e-2cc3-40d9-8739-96cdbc760a13
📒 Files selected for processing: 2
src/main/git/worktree-removal-real-git.test.tssrc/shared/git-branch-cleanup.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 6 remain after this review.
| 'diff', | ||
| '--name-only', | ||
| `${targetOid}...${branchRef}` |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Include committed submodule changes in the ancestry decision.
If diff.ignoreSubmodules=all is set, this diff can omit a branch’s gitlink update. A squash-merged branch that changes only that gitlink then appears to have no changes. The ancestry check rejects the branch, so cleanup preserves it instead of reaching the merge-equivalence checks. Pass --ignore-submodules=dirty or none to detect committed gitlink updates regardless of that setting. (git-scm.com)
* fix(workspace): show launch actions in empty tab groups * fix(workspace): resolve empty-pane browser policy by owner
* 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
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…#32216) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* 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: reuse bounded Ubuntu mirror setup for WebKit * ci: preserve Ubuntu setup for dispatched older refs
… 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
* 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 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 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
#32263) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ther 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>
* fix: name Codex subagents from rollout metadata * Hide generic default role in child agent rows * Reject non-regular Codex identity transcripts without blocking
* 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: 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
…32269) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…es, 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
…try 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
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>
…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.
…n 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>
* 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: 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>
* 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>
ELI5
Removing a worktree can currently delete a branch whose only unmerged history is empty commits, or commits whose file changes cancel out. Keep those branches and their saved heads instead of treating unchanged files as proof that their history was merged.
What Changed
The fallback after Git refuses
branch -dnow checks whether the branch contributed any net file changes since its merge base. If it did not, cleanup requires actual commit ancestry before using the existing tree/patch equivalence checks. A failed comparison also requires ancestry proof. Integration with current main retains its early common-ancestor guard, reuses the computed merge base, and preserves target-fetch/refspec/offline authority. The existing preserved-branch result and warning handle refusal; normal merged and squash-merged branch cleanup remains available.Why
The old fallback could prove that merging the branch changes no files, then remove its ref through
update-ref. An unmerged empty commit also satisfies that tree test. A real-Git regression reproduced this through productionremoveWorktreeon the current base, returning success rather than preserving the branch.Linked Issue
Refs #20875. Addresses the later report's unmerged
--allow-emptyreproduction and the equivalent change/revert case. The broader request to keep all branches by default, and the CLI help text, remain outside this change; this PR does not claim full issue closure. Mixed history with squash-merged nonempty changes followed by an unmerged empty commit still has a nonempty merge-base diff and retains the existing tree-equivalence cleanup policy.Visual Proof
N/A — this changes Git branch retention and the existing CLI removal result; no rendered UI or interaction changes.
Testing
git diff --checkpass.All test commands used
ORCA_BACKGROUND_LAUNCH=1. No user worktrees or branches were removed.Review
Self-reviewed the fallback's failure behavior, expected-head deletion guard, existing squash cleanup, and host routing. No RPC or persistence schema changes. The added Git commands support the documented Git 2.25 baseline and use the existing execution-host wrapper, including WSL routing. Paired/mobile callers use the host's existing removal path; no client change is required. Folder workspace removal is separate. Remote SSH removal has not been manually exercised.
Agent skill upstream boundary
Checklist