Skip to content

fix(daemon): retain a flat per-session scrollback window - #13061

Merged
brennanb2025 merged 9 commits into
mainfrom
brennanb2025/daemon-scrollback-budget
Aug 13, 2026
Merged

brennanb2025 merged 9 commits into
mainfrom
brennanb2025/daemon-scrollback-budget

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The terminal daemon retained ~5000 rows of xterm grid per live session with no bound, so memory scaled with an unbounded session count. On a workstation running many agent worktrees the daemon reached ~1.9 GB, was killed under system memory exhaustion, and took every session it owned with it.

The fix: a flat, small per-session scrollback window. Daemon sessions retain 1000 rows, set once at creation. A terminal the user has open scrolls its full live renderer buffer regardless; the daemon window is what a rebuild (window reload, pane remount, app restart, remote/mobile attach) restores.

Why 1000, and why 5000 was never a real decision

  • The daemon's previous 5000 had no independent rationale: it mirrored the renderer's default preset, which Migrate terminal scrollback setting from bytes to rows #6593 chose when migrating the user setting from a legacy 1 MB byte cap to row presets. The durable host inherited that number for symmetry — per-session retention in the daemon was never deliberately sized until this PR.
  • Measured cost (bench in perf(serve): bound memory for headless orca serve #13684, N=50): 1.02 MB/PTY at 5000 rows vs 0.12 MB at 1000 — an 88% per-PTY reduction, growing with width/attribute-heavy agent output (~1–3 MB/PTY real-world at 5000).
  • Worst case at the incident's 100+ sessions: ~100k retained rows total, versus ~500k before.

Agent conversations, explicitly considered

Inline-rendering agent CLIs write their conversation into normal-buffer scrollback, so on a rebuild an agent transcript longer than the window shows only its newest 1000 rows. This was judged acceptable to ship because (a) the durable conversation record is the agent's own transcript file, which the native-chat subsystem reads in full, plus agent resume; (b) the old 5000 already truncated long agent sessions on rebuild — any RAM window does; and (c) raising the window for agent sessions specifically would rebuild the incident on exactly the agent-heavy hosts this PR protects. The product answer for full-depth agent scrollback is disk-backed session history (bounded per-session file, rebuilds replay from disk), tracked as a separate designed project; this window then becomes its RAM cache.

Honesty about the incident's composition

Per the #13684 bench, clean grid at 5000 rows accounts for ~100–300 MB at the incident's session count — the dominant scaling term this PR bounds, but not the full 1.9 GB, which included width/attribute-heavy content and other per-session terms. Daemon memory instrumentation is a follow-up.

Two earlier revisions of this PR live in its history and were deliberately replaced: an even-split row budget (shallowed every terminal as the count grew) and an LRU parked-session retention (preserved deep rebuilds for recent sessions, at the cost of recency tracking and runtime trimming — moving parts with failure modes of their own, one of which reviews caught live). The final design deletes all of it: one constant, one bounded override.

Retained from those revisions because they fix real pre-existing bugs regardless of retention policy: transport-drop attachment release (protocol detach was a logging-only TODO; a dropped client left attachedClients entries forever), the attach-vs-disconnect race guard, and session-exit bookkeeping moved to the host reap hook (idle shutdown previously only worked for unattached sessions because of the attachment leak).

Related: #13684 bounds serve-side memory and is complementary — it feeds a smaller default to serve-mode runtime emulators, a surface this PR deliberately leaves untouched (history replay must handle old deep checkpoints). It will need a rebase once this lands, since daemon sessions then always receive an explicit window.

Screenshots

No visual change for open terminals (live renderer buffer). Behavior change on rebuild-from-daemon: restores 1000 rows instead of 5000. QA evidence in PR comments (latest comment tests this final design; the two earlier QA comments tested superseded designs and are historical): live terminal scrolls to line 1 of 3000 with no reload; a real remount restores the ~1000-row window with newest content intact.

Testing

  • pnpm lint
  • pnpm typecheck
  • pnpm test — full src/main/daemon suite: 1355 passed, 3 skipped, 0 failures. Full-repo run not executed locally.
  • pnpm build — not run.
  • Added or updated high-quality tests

Window default/override bounds (inclusive [100, 5000], malformed falls back); end-to-end host test proving retained rows cap at the window with newest content surviving; the v29→v30 history-handoff fixture pins old-daemon depth explicitly so the chunked-seed path stays covered; transport-drop release regression-tested against the unfixed daemon.

AI Review Report

Three until-clean review rounds across this PR's three designs, all with same-model high-reasoning subagents; the final flat-window round verified: the window reaches exactly the one production Session creation site and only daemon sessions (history-reader scratch and runtime emulators keep prior depth so old deep checkpoints replay fully); deletion completeness for all removed retention symbols; the kept transport/detach/idle-shutdown fixes intact and tested; chunked history-seed transfer reachable where it matters; override clamping. One finding (stale retention-era comments) fixed in cfa9e98. Cross-platform: no platform branches; identical on macOS, Linux, Windows; the incident host was Windows.

Security Audit

No input handling, command execution, path, auth, secrets, or IPC surface changes. One env var (ORCA_DAEMON_SESSION_SCROLLBACK_ROWS) parsed as a strict bounded integer; out-of-range falls back to the default. No new dependencies.

Notes

Restore-depth expectation changes on rebuilds only (5000 → 1000). Follow-ups tracked separately: disk-backed session history (full-depth agent scrollback + history surviving daemon death), daemon memory instrumentation, daemon crash-loop guard, build-tool memory backpressure.

The daemon retains ~5000 rows of xterm grid per live session with no aggregate
bound, so retention scales with an unbounded session count. A host owning 100+
terminals held ~1 GB of grid, was killed under system memory exhaustion, and took
every session it owned with it.

Split a fixed row budget across live sessions instead: full depth until the budget
binds, then an even share down to a floor that still restores the command which
produced the visible screen. Re-applied on create and reap, so depth returns to
survivors as terminals exit.

The emulator itself stays load-bearing (on-disk history is a projection of it), so
this bounds the aggregate rather than lowering the per-session default.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The daemon now defines configurable scrollback depths and selects least-recently-viewed parked sessions for trimming. HeadlessEmulator and Session expose retained scrollback updates, while terminal mode collection uses shared helpers. TerminalHost tracks session recency and reapplies retention during lifecycle events. Daemon attachments now use ownership tokens and are released on transport loss. Tests cover retention behavior and attachment cleanup races.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is detailed and relevant, but it omits the required template sections and does not provide the mandatory linked issue. Use the repository template headings, add the required issue link, complete AI Disclosure, Review, Checklist, and Author sections, and state N/A for visual proof with a reason.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title identifies daemon scrollback retention, but it inaccurately describes a flat window and omits the parked-session LRU policy.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/main/daemon/daemon-scrollback-budget.ts (1)

3-11: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Shorten the rationale comments.

Keep the retention rationale in one concise comment. Move incident details and heap estimates to documentation.

As per coding guidelines, “Comments must be concise, limited to non-obvious information, and preferably one line.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f44f0b1-eb22-456e-bcfc-4c4cbc44a3ac

📥 Commits

Reviewing files that changed from the base of the PR and between 6c9215a and 2972ac4.

📒 Files selected for processing (7)
  • src/main/daemon/daemon-scrollback-budget.test.ts
  • src/main/daemon/daemon-scrollback-budget.ts
  • src/main/daemon/headless-emulator-modes.ts
  • src/main/daemon/headless-emulator.ts
  • src/main/daemon/session.ts
  • src/main/daemon/terminal-host-scrollback-budget.test.ts
  • src/main/daemon/terminal-host.ts

Comment thread src/main/daemon/daemon-scrollback-budget.test.ts Outdated
Comment thread src/main/daemon/daemon-scrollback-budget.ts Outdated
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Electron QA — daemon scrollback budget (PR #13061)

Build verified: CDP attach to http://127.0.0.1:9340 → window.api.app.getIdentity() returned
pr-13061-scrollback-budget @ brennanb2025/daemon-scrollback-budget (worktree path matches this PR). Screenshots below were taken from that instance only.

Method: electron-vite dev with REMOTE_DEBUGGING_PORT=9340, clean temp profile, playwright-cli over CDP. No product code changes.

What was exercised

Scenario Result
1. Normal case (few terminals) PASS. 1 session: generated 2500 unique marker lines; getMainBufferSnapshot(..., {scrollbackRows:5000}) retained lines 1–2500 including start/end markers. Terminal UI responsive.
2. Budget-crossing load Partial / inconclusive for depth cut. Opened 101–102 live sessions (budget binds at ~26; expected even share ≈1254 rows at 102). Terminals still spawn, accept input, and remount. Could not visually prove per-session depth dropped via pty.getMainBufferSnapshot (see caveats).
3. Open/close stress PASS (no crash/stall). Rapid open 15 + close 15 while ~102 already open: ended at 102 tabs / 102 sessions, app identity still this PR, daemon process still alive.
Unit tests PASS. daemon-scrollback-budget.test.ts + terminal-host-scrollback-budget.test.ts: 18/18.

Daemon RSS (this PR only — not vs main)

Checkpoint Daemon RSS (KB)
Baseline (~1 session, after 2500-line fill) 72,704
After ~41 empty-ish sessions 76,480
At 101 sessions 65,920–77,808 (GC noise)
After filling 20 sessions × 3000 lines 123,888 peak
After open/close stress 130,800 peak / settled ~72k

Daemon stayed well under the multi-GB incident class with 100+ sessions. Did not run the same heavy scenario on main in this QA pass, so this is not a controlled before/after comparison — only “this build under load stayed modest.”

Screenshots

Few terminals — deep output present (end of 2500-line fill):

01-few-terminals-after-output.png

Many terminals open (41):

04-many-terminals-open-41.png

Budget-path terminal after 4000-line fill (still shows recent history / prompt):

05-budget-terminal-after-4000-lines.png

101 sessions open (tab overflow / scroll controls visible):

06-many-terminals-101-sessions.png

Remount / reattach Terminal 1 (history still present):

07-after-remount-terminal1.png

Terminal 1 still live with ~100 tabs open:

12-terminal1-with-many-open.png

After open/close stress (app still healthy):

13-after-open-close-stress.png

Caveats / what I could not verify

  1. Visual proof that scrollback depth shrinks under budget is incomplete.
    At 102 sessions (expected ~1254 rows), getMainBufferSnapshot(ptyId, {scrollbackRows:10000}) still returned full 2500–3000 marker lines on sampled sessions (source: "headless").
    That IPC path prefers the provider snapshot only after a dataGap; otherwise it falls through to main’s serializeHiddenOutputRecoveryBuffer (also labeled headless). So this API is a weak probe of the daemon’s budgeted emulator. I did not get a clean, authoritative “daemon buffer length after reapply” reading from the live app.

  2. No side-by-side RSS run on main. Memory evidence is “this branch stayed small under 100+ sessions,” not “X% better than main.”

  3. UI scroll-to-top of deep history was weak (canvas xterm; PageUp did not clearly expose earliest markers in the viewport). Depth was verified via snapshot data for the few-terminal case, not purely by screenshot of line 0001.

  4. Renderer scrollback / on-disk history were not separately audited; this PR bounds the daemon headless grid.

Bottom line

  • No regression observed for the common case (few terminals keep full depth markers).
  • Large session counts work without crash/stall; daemon RSS stayed far below the incident regime.
  • Unit tests for budget tiers + host reapply pass.
  • Cannot claim live visual confirmation that retained depth actually drops to the even-share floor with the probes used; worth a follow-up that hits the daemon getSnapshot path (or forces provider-required snapshot) after crossing the budget.

…rked LRU overflow

Replace the even-split budget: dividing a fixed row budget across all sessions
shallowed every terminal as the count grew, so a user with 60 terminals silently
lost half their reachable scrollback — depth degradation with no signal.

Retention now trims by attention, not arithmetic. Attached sessions always keep
full depth and never consume the cap. Parked sessions keep full depth up to a
cap of 24, least-recently-viewed evicted first down to 1000 rows — enough to
reattach with the recent command context on screen. A reattached session
returns to full depth for everything it emits afterward, and a freed slot
returns the newest trimmed session to deep retention.

Worst case is bounded at cap x full depth plus trimmed remainder, the same
memory class as the old budget, without ever shallowing a terminal the user is
looking at.
@brennanb2025 brennanb2025 changed the title fix(daemon): bound retained scrollback across sessions fix(daemon): retain full scrollback for viewed sessions, trim parked LRU overflow Aug 10, 2026
…nnot pin full depth

The attached-client exemption made an attachment that outlives its transport a
permanent full-depth pin: protocol detach was logging-only, and neither control-
nor stream-socket loss removed the Session.attachedClients entry. Enough dead
attachments would silently rebuild the unbounded retention the LRU cap prevents.

Track the attach token per session, release exactly the dropped client's
attachments in one batched retention pass, implement protocol detach for real,
and cancel an attach whose client vanished mid-flight. Move session-exit
bookkeeping to the host reap hook — it previously lived in the per-attachment
exit callback, which only fired for unattached sessions BECAUSE of the leak, so
fixing the leak made an unattached session's exit invisible to idle shutdown.

Also drop the create-time double recency increment.

Co-developed with a review pass; transport-drop release is regression-tested
against the unfixed daemon (fails without, passes with).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/main/daemon/daemon-server-attachment-lifecycle.test.ts (1)

118-120: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the cancellation error.

Lines 118-120 convert every createOrAttach failure into success. The test does not verify the required TerminalAttachCanceledError result. Assert that request rejects with TerminalAttachCanceledError after finishRequest().

src/main/daemon/terminal-host.ts (1)

108-118: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Add an authoritative daemon-buffer regression test.

The current QA evidence did not prove that parked sessions are trimmed because snapshot APIs may read another buffer. Add a test that parks a session, exceeds DAEMON_SCROLLBACK_TRIMMED_PARKED_ROWS, triggers a lifecycle retention pass, and reads the authoritative daemon snapshot. Also verify reattachment behavior for subsequent output.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2d1e611e-bb4a-4f3d-990b-807535fb686f

📥 Commits

Reviewing files that changed from the base of the PR and between 77a4115 and 0df7989.

📒 Files selected for processing (4)
  • src/main/daemon/daemon-server-attachment-lifecycle.test.ts
  • src/main/daemon/daemon-server.ts
  • src/main/daemon/daemon-transport-attachment-release.test.ts
  • src/main/daemon/terminal-host.ts

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Electron QA — attention-based scrollback retention (post design change)

Build under test: 0df7989556 on brennanb2025/daemon-scrollback-budget
CDP identity verified: devLabel=pr-13061-scrollback-budget @ brennanb2025/daemon-scrollback-budget, devRepoRoot=.../pr-13061-scrollback-budget
Method: isolated ORCA_DEV_USER_DATA_PATH Electron dev instance on CDP :9333 / renderer :5180. Depth checked via window.api.pty.getMainBufferSnapshot(ptyId, { scrollbackRows: 5000 }) plus full-window screenshots. No product code changed.

1) Headline — actively used terminal keeps full history with 30+ sessions ✅

  • Filled Terminal 1 with numbered lines QA_LINE_0001 … QA_LINE_4500 while viewing.
  • Opened 32 additional terminals → 34 live sessions total.
  • Daemon snapshot after mass-create and after re-selecting Terminal 1: still 4500/4500 markers, has0001=true, has4500=true.
  • UI scroll-to-top on Terminal 1 with all 34 tabs still open: earliest lines still present (QA_LINE_0001 visible at top of viewport).

After fill (bottom of history):

qa-scrollback-01-after-fill.png

After 34 sessions + reattach + scroll to top (earliest lines intact):

qa-scrollback-05-headline-max-scroll.png

Hard requirement satisfied: a terminal I’m using still scrolls all the way back with far more than 24 terminals open.

2) Recently-viewed reattach keeps full history ✅

  • Filled Terminal 30 with RECENT_LINE_0001 … RECENT_LINE_1500.
  • Switched away to Terminals 31–34, then reattached to Terminal 30.
  • Daemon snapshot after reattach: 1500/1500, has0001=true, has1500=true.
  • UI scroll still shows deep recent history (screenshot mid-scroll; daemon snapshot is the full-depth proof):

qa-scrollback-04-recent-t30.png

  • Terminal 3 (filled earlier with 2500 PARK_LINE_* markers) also retained 2500/2500 including 0001 while other tabs were active.

3) Long-parked past LRU cap → ~1000 rows ⚠️ partial (unit-tested; not forced live)

Live Electron path: With 34 open tabs in one connected client, sessions remained reachable at full depth (no live observation of a trim-to-1000). In this topology the desktop client appears to keep stream ownership for open sessions, so the “parked/detached” LRU trim does not fire merely from switching tabs.

Daemon policy (automated): host + pure policy tests all pass on this checkout:

  • src/main/daemon/terminal-host-scrollback-retention.test.ts — 5/5 passed (full depth under cap; only LRU-oldest parked trimmed past cap; attached never trimmed; reattach restores full depth going forward; freed slot returns newest trimmed to full).
  • src/main/daemon/daemon-scrollback-retention.test.ts — 8/8 passed.

I did not live-prove a real “parked past cap → reattach shows ~1000 recent rows” UI screenshot in this run. That path is covered by host tests with explicit detach(), not by open-tab UI.

4) Stability / daemon RSS at 30+ sessions ✅

  • Opened/closed-switch among 34 terminals: no crash, no stall, no freezes.
  • Daemon PID for this instance (daemon-entry.js under this worktree’s profile): ~101–114 MB RSS at 34 sessions (sample ~113.7 MB near end of run). Far below the ~1.9 GB incident class. Note: most of those sessions were near-empty shells; only a few held multi-thousand-line buffers.

5) Regression — few-terminal full depth ✅

  • Before mass-create, Terminal 1 already held full QA_LINE_0001…4500 at default depth (daemon snapshot + bottom screenshot). Behavior matches pre-change expectation for normal few-terminal use.

Not verified live

Item Status
Parked-beyond-cap trim to ~1000 rows in real UI Unit/host tests only; open tabs did not enter detached park
Dead client transport releases attachments so a dead client cannot pin full depth Code path present (detachClientSessions on transport drop); not exercised in this Electron session
Windows host (incident platform) macOS only
Multi-client (desktop + mobile) attach/park interaction Not tested

Verdict

Headline hard requirement passes on this build: with 34 live sessions, the terminal in use retains and can scroll back to QA_LINE_0001. Recently-viewed reattach also keeps full filled history. Daemon RSS stayed modest. Parked-cap trim and transport-drop release are supported by tests/code but were not reproduced as live UI screenshots here — call that residual risk, not a headline regression.

terminal-host.ts crossed the 300-line cap; the entry-building and depth
application belong with the selection policy anyway, leaving the host with
only the recency bookkeeping it uniquely owns.
…budget

The previous commit accidentally swept QA screenshots and a report from the
repo root into the tree (root-directory-guard failure), and headless-emulator
had crept to 301 counted lines. Screenshots live on the PR via the attachments
CDN, never in the tree. The retained-scrollback trim moves to
headless-emulator-modes with the OSC-link shift as a pure helper.
Replace the LRU parked-session retention with what every peer terminal ships:
a flat, small per-session window in the durable host. Sessions retain 1000
rows — the generous end of what terminal products restore on a rebuild — and
deep scrolling on an open terminal remains the renderer's live buffer.

This deletes the dynamic-retention machinery entirely: no recency tracking, no
attached-exemption, no runtime trimming, no OSC-link shift on trim. The window
is set once at session creation. Worst case at the incident's 100+ sessions is
~100k rows of grid, versus ~500k before.

The bounded env override can tune the window within [100, 5000]; anything
outside falls back to the default, since an unbounded daemon is the failure
this window exists to prevent.

The v29->v30 history-handoff fixture now pins the old-daemon depth explicitly:
it plays an old binary whose sessions retained ~5000 rows, and the new flat
window would otherwise shrink its history below the chunked-seed threshold and
silently skip the transfer path the test exists to cover.
@brennanb2025 brennanb2025 changed the title fix(daemon): retain full scrollback for viewed sessions, trim parked LRU overflow fix(daemon): retain a flat per-session scrollback window Aug 11, 2026
@brennanb2025

brennanb2025 commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor Author

QA: flat 1000-row daemon scrollback window (HEAD cfa9e986b7)

Verified against the final design (not the historical even-split / LRU designs): every daemon session retains a flat 1000-row scrollback window set at creation; an open terminal keeps the full live renderer buffer; rebuild (reload/remount/restart/remote attach) restores only the daemon window.

CDP identity confirmed: Orca: brennanb2025/daemon-scrollback-budget / worktree pr-13061-scrollback-budget.

1) LIVE TERMINAL UNAFFECTED

Filled Terminal 4 with 3000 numbered lines (python3 .qa/fill_lines.py), scrolled to top without reload.

  • Live xterm: scrollback=5000, buffer length 3011, LINE-0001…LINE-3000 all present
  • Scrolled so history starts at LINE-0001 (renderer buffer, not daemon window)

Live scroll — LINE-0001 at top, no reload

2) REBUILD RESTORES THE WINDOW

Called remountTerminalTabForRecovery on the same tab (rebuild-from-daemon path).

  • After remount: buffer length 1056 (baseY=1000 + 56 rows), 1054 LINE-* rows
  • History now starts at LINE-1947 (oldest ~1000 rows + viewport gone)
  • Newest present (LINE-3000, FILL_DONE, prompt); oldest (LINE-0001…LINE-1946) absent
  • Terminal still usable (prompt responsive)

After remount — history starts ~LINE-1947

3) Multi-terminal smoke + daemon RSS

  • Opened 15 terminals; all got PTYs; light echo writes on each — no crash
  • Daemon for this profile (daemon-entry.js under this worktree’s temp user-data): RSS ≈ 124 MB (pid 83887, macOS ps RSS KB)

Notes / honesty

  • No product code changed; QA only.
  • One earlier tab was double-filled past the live 5k scrollback, so its live top was LINE-0952 rather than 0001; the clean single-fill tab is the evidence above.
  • Remount used generation-bump recovery (remountTerminalTabForRecovery), which reattaches the existing daemon session — the right rebuild path for this check.

Verdict: Behavior matches the flat-window design. Live depth is intact; rebuild is capped ~1000 rows.

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