Skip to content

[Bug]: Editor tabs won't stay closed — duplicate openFilesByWorktree + multi-identity unifiedTabs resurrect siblings (1.4.193) #17778

Closed as duplicate

Description

@shahidbeig-a11y

Environment

  • OS: macOS 26.5 (Apple Silicon)
  • Orca: 1.4.193
  • Repro surface: Matri agent-platform reactor worktree (c5adca14-…::…/matri-agent-platform), also visible on Factory reactor

Summary

Closing editor tabs for certain files does not stick. The tabs reopen / pop back (user-visible on Matri reactor for agents/reporting-agent/src/reporting.ts and docs/runtime-interface-approval.md).

This is not pin-confirm UX (isPinned is false). It is session-store resurrection of sibling editor identities.

Classification (relatedness)

Issue Domain Same bug?
#17775 Main CPU / git.exec + FS storm No — different symptom and path
#17776 Orphan / path-missing worktreeMeta, no CLI remove No — dead meta, not editor-tab identity
Factory agent orphan-store work Dangling registered worktrees / meta sweep No — different store maps
This issue Editor session persistence: duplicate openFilesByWorktree + multi-identity unifiedTabs Yes (new / third)

Shared backdrop: large workspaceSession maps and multi-runtime pairing make the bug easier to hit, but the close/hydrate contract for editors is an independent defect.

Observed durable state (orca-data.json → workspaceSession)

Duplicate openFilesByWorktree rows (same path + same runtime, repeated)

Worktree File Copies
matri-reactor runtime-interface-approval.md 4
matri-reactor reporting.ts 3
factory storage-admission.ts 5
factory 835-design.md 4

Hydrate code already comments that repeated (path, worktree, runtime) tuples are corruption (hydrate-editor-session.ts), and skips duplicates on load — but the store keeps rewriting them, and unifiedTabs is not kept in lockstep.

Dual / multi unifiedTabs identities for one entityId

Same file path appears under more than one tab id:

  • Legacy bare path id: /Users/…/reporting.ts
  • Owned id: editor:{worktree}:{runtimeEnvironmentId}:{filePath} (buildOwnedEditorFileId)
  • Stale second runtime env id d289656c-… and current paired Dell env 189aaae7-… (only 189aaae7 remains in orca-environments.json)

Example (matri-reactor reporting.ts): both the bare path id and the editor:…:189aaae7-…:… owned id are present for the same entityId.

isPinned is false on these rows.

Why close does not stick (code pointers)

  1. closeFile(fileId) removes one openFiles entry by exact id, then finds one unifiedTabs entry with entityId === fileId and closeUnifiedTab, then break (close-file-action.ts). Sibling identities for the same path stay in the store.
  2. Persist path writes openFilesByWorktree without stable ids (path + runtimeEnvironmentId only). Hydrate rebuilds ids via collision / owned-id rules (hydrate-editor-session.ts, editor-file-ids.ts).
  3. unifiedTabs keeps older id forms (legacy path id + owned ids for retired runtime env d289656c-…). After a partial close, hydrate / reconcile / host mirror can resurrect the remaining siblings.
  4. Product comment already admits reopen risk for mirrored editors: "the host must close its copy or its next snapshot re-mirrors the file and the tab reopens" (close-file-action.ts / recently-closed-editor-tabs.ts).

Expected

  1. Closing an editor tab removes all session identities for that file owner (every openFiles row + every unifiedTabs row sharing the path/entity), including stale-runtime owned ids.
  2. Persist must not accumulate duplicate (filePath, runtimeEnvironmentId) rows in openFilesByWorktree.
  3. Hydrate / migrate should collapse legacy bare-path ids and owned ids for the same owner, and drop owned ids whose runtimeEnvironmentId is no longer a known environment.
  4. Users should not need to quit Orca and hand-edit orca-data.json to make a close stick.

Local workaround intent

Operator-side detector will alert when sticky identity count exceeds a threshold and offer a quit+surgical clear. That is mitigation only — product fix still needed in close/hydrate/persist.

Related

Activity

  1. shahidbeig-a11y commented on Sep 1, 2026

    @shahidbeig-a11y
    ContributorAuthor

    Operator mitigation (local)

    Installed orca-sticky-tab-watch on this Mac:

    • Detects duplicate openFilesByWorktree rows + multi-identity unifiedTabs
    • Pops when sticky identity count > 3
    • Show list → scrolling choose from list
    • Quit & Clear → quit Orca, surgically dedupe session store (backup first), reopen
    • Ignore → 60m snooze
    • LaunchAgent com.mo.orca-sticky-tab-watch every 5 minutes

    This is mitigation only; product fix still needed on close/hydrate/persist as described above.

  2. shahidbeig-a11y commented on Sep 1, 2026

    @shahidbeig-a11y
    ContributorAuthor

    Cross-host confirmation (2026-08-31)

    Same sticky / dual-runtime resurrection is present on Dell Orca as an independent store — not only on the Mac host that filed this.

    Env pairing (each host names the other)

    Host Local store Known remote env
    Mac ~/Library/Application Support/Orca/profiles/local-default/orca-data.json 189aaae7-… = Dell Orca
    Dell ~/.config/orca/profiles/local-default/orca-data.json d289656c-… = Mac Orca

    Live Dell sticky (store mtime ~19:58 PT, detector threshold 3)

    • Router-and-Hooks / anthropic_usage.py — openFiles ×4 stamped runtime=d289656c (Mac’s id on Dell)
    • matri-reactor / PRD.md — openFiles ×5 stamped runtime=d289656c
    • unifiedTabs multi-id for the same entity: legacy UUID + owned-…:d289656c-… + owned-…:189aaae7-…

    Live Mac sticky (same detector, same window)

    • matri-reactor / runtime-interface-approval.md — openFiles ×2 stamped runtime=189aaae7 (Dell’s id on Mac)
    • Factory 835-design.md / storage-admission.ts — duplicate openFiles + owned twins for both 189aaae7 and stale/local d289656c

    Read

    Closing one tab id on either host leaves the cross-runtime sibling in that host’s store; hydrate / host-mirror can reopen it. Pairing Mac↔Dell makes the twin-id pattern the default, not an edge case.

    No clear/quit performed for this check — read-only copies only.

  3. rikimaru63 commented on Sep 7, 2026

    @rikimaru63

    Additional reproduction on Orca 1.4.197, source base 04aa03ad620206ec13b00d5204f14ea579b1b7b9 (macOS arm64). This distinguishes two intentionally separate editor views from corrupt duplicate identities.

    Using disposable profiles and a paired Electron client, I created two Markdown editor tabs with different tab IDs but one file entity, plus a live terminal. The test clicks the client's visible close button for the second view. It delays only that real session.tabs.close RPC, opens a marker file on the host to force a newer snapshot containing both original views, then releases the RPC.

    Two distinct paths need attention:

    1. In useTabGroupTabCloseCommands.ts, the shared-file branch must send the exact view ID and owner to the host and record the close intent. Otherwise closing only the local unified tab lets a host snapshot restore it.
    2. Once that notification arrives, closeMobileSessionTabInStore in runtime/mobile-session-tab-close.ts calls store.closeFile(unifiedTab.entityId) even for an exact view ID. The real host snapshot then loses the remaining legitimate view too. Before the host-side fix, the isolated E2E reached the correct host RPC and resisted resurrection during the delay, but failed because only the marker file remained after the host processed the close.

    For an exact tab ID with another editor view referencing the same entity, the host should close that unified view while retaining the shared file and draft. The final-view and legacy file-ID paths still need their existing file cleanup. Exact tab IDs must also take precedence over another tab's matching entity ID.

    A local candidate covers those branches. The host regression tests use the real store and check that the other view, unsaved draft, and terminal survive, and that final-view cleanup still works. The candidate has not been installed into the working Orca app. This does not establish a fix for every stale-owner identity, PDF case, restart path, or reported selection flicker.

    After rebuilding, the paired-Electron test passed. A final run also waited for the retained view's actual body text to be visible. The host snapshot advanced through the delayed close, removed only the requested view, and retained the other view plus the same terminal surface ID and handle. The snapshot does not supply terminal incarnation IDs, so this is not a separate proof of process-incarnation continuity. The 58 focused store/cleanup tests, existing web TypeScript check, and targeted lint also passed. No working profiles or client files were used in these tests.

  4. shahidbeig-a11y commented on Sep 19, 2026

    @shahidbeig-a11y
    ContributorAuthor

    Field update — Orca 1.4.205 (Linux) / 1.4.204–205 (macOS), four paired hosts, 2026-09-18

    Still reproduces on 1.4.205, so #17370 (dedupe at restore) does not cover it. New measurements that may help #21124 / #21125 / #10926. Paths, hosts and ids are masked.

    1. The store grows on every write, not only at restore

    Same file, same worktree, same runtimeEnvironmentId — byte-identical openFilesByWorktree rows. References to one file path across consecutive writes of orca-data.json:

    write refs
    n 39
    n+1 52
    n+2 65
    n+3 78

    Exactly +13 per write. In a controlled run I cleaned the store to 1 row per host with Orca fully down, started all hosts, and let them sync: 1 → 21 rows in about 2 minutes, with no user action. This is the write-path leak that #21124 describes; the field data supports merging identical rows before the write.

    2. Zero local origin — every row was a mirror of a mirror

    On all four hosts, 42 of 42 openFilesByWorktree rows had runtimeEnvironmentId set. No host held the file as a local tab. The file exists on one host only; the other hosts do not have the path at all. So:

    Note for #21277: it keeps selector_not_found retryable (correct for a transient miss), so this case stays open after that merge. Here the miss is permanent — the path does not exist on the receiver.

    3. Environment ids are per-host, so "unknown id" is not "dead id"

    Each host gives the same peer a different local environment id. I first read the unfamiliar ids inside editor:<worktree>:<env>:<path> tab ids as dead environments. They were live peers under the other host's name for them. This is the perspective problem of #10859; any cleanup that compares ids across hosts will be wrong.

    4. Why de-duplication alone does not hold (workaround data)

    Offline dedupe of openFilesByWorktree + unifiedTabs (the workaround from #17185) failed three times: one surviving mirror row per host is enough seed for the echo to regrow. What held:

    1. Stop Orca and its daemon on all paired hosts at the same time.
    2. On every host, remove every editor row/tab that has a runtimeEnvironmentId (all mirrors, no survivor), prune the ids from tabGroups[*].tabOrder / recentTabIds / activeTabId, activeTabIdByWorktree and activeFileIdByWorktree. Leave contentType: "terminal" alone.
    3. Start the hosts.

    Result: 0 rows on every host after 3 minutes of sync (before: 21 in 2 minutes). Mirrors are derived data, so a file that is really open on a peer comes back by itself. One missed host re-seeds all the others.

    Suggested acceptance case for the open PRs

    Three reciprocally paired hosts; a mirrored editor row for a path that exists on none of the receivers; no local origin on any host. Expected: the row count does not grow across sync rounds, and a close on any host is final.

    AI disclosure: measurements and write-up were produced with an AI coding agent working on my hosts; I checked the result on the machines.

  5. added theissue type on Sep 19, 2026
  6. nwparker commented on Sep 27, 2026

    @nwparker
    Contributor

    Closing as a duplicate of #21121, which consolidates the editor-tab resurrection and duplicate session identity reports. The cross-host mirror echo evidence from this issue has been carried over to #21121 as an acceptance case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions