Repository navigation
(sidebar): hide sessions launched through the Agent SDK - #486
Conversation
Programs driving Claude through the Agent SDK (headless claude -p runs, review tools spawning one session per file batch) write top-level transcripts with no link to the session that started them, so they flooded project groups as unrelated sessions. Keep the entrypoint of the first user turn in session_cache and leave sdk-* rows out of the project list while the new hideSdkSessions setting (default on) is set. Scheduled runs stay visible: their first turn is pre-seeded without an entrypoint. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
Subagents of a hidden SDK session fell into Orphan subagents and kept SDK-only projects listed. A session resumed from a terminal stayed hidden because only its first turn counted, and the header-only refresh never saw the later turn: count any cli turn, and re-read cached SDK rows in full. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
A 13 MB live SDK transcript re-read in full costs ~250 ms of main thread per watcher flush; above 2 MB keep the header-only refresh. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
|
@devsuitup ready for review: CI is green and an internal review loop converged with no remaining findings (its fixes are in 5394ae8 and 843f633). |
devsuitup
left a comment
There was a problem hiding this comment.
Review at 843f633. The goal is clear and the filter is in the right place (the rows stay in session_cache, session_metrics and FTS, so the heatmap and token totals keep counting that activity). The branch is level with main, and 224 tests in 22 related files pass locally on Node 24. Four points block; each was traced by the reviewer in the shipped code, and I re-read the two I could check directly.
Blocking
- An SDK session the user has open disappears from under them (
session-cache.js,buildProjectsFromCache, thehiddenSdkIdsskip at ~481). Turning the setting on (or on the first rebuild) removes an open SDK session from the sidebar and grid listings, and a cold restore treats a saved SDK session as unavailable. A session that is open in a terminal, or in the saved working set, must stay listed. Test: open ansdk-*session, then check that it is still listed with the setting on. - An interactive continuation of an SDK session never makes the parent visible (
session-cache.js~447). A resumed transcript whose later records saycli, or a compaction mirror, keeps the parent hidden even after a rebuild. The first-userrecord alone decides, so a session the user continued by hand can be lost. Decide on the whole transcript (or treat a laterclirecord as interactive) and test it. - The schema migration clears the cache before the single-instance lock (
db.js~313 and ~345,main.js~3278). Adding theentrypointcolumn setsmustReindex, which runsDELETE FROM session_cacheandDELETE FROM cache_metawhendb.jsis loaded, beforerequestSingleInstanceLock. A second launch that is refused still empties the database of the running instance, and the running version does not know the new column. ThefileMtimemigration already works that way, but a new column should not repeat it: take the lock before opening the database, or make the column added without a wipe (a NULL entrypoint means "unknown, rescan lazily"). - A failure before the first scan result leaves zero cached sessions (
db.js~345). After the wipe there is no previous cache to fall back to, so an interruption or an error during the scan shows an empty app until the next successful scan. Keep the old rows until the new scan has written its results, or rebuild into the same rows.
Non-blocking
- A resumed SDK transcript above 2 MB stays hidden until a full rebuild (
session-cache.js~247). - A project that holds only SDK sessions keeps its header in the sidebar, empty (
session-cache.js~579). - A non-string
entrypointin a record may make the bind fail (db.js~561); validate it to a string or null before storing.
Not run: Linux Node 20/22 with c8, a large-cache benchmark of the one-time reindex, lint, fresh CI. CI on this head is not approved yet; I will approve it once the blocking points are fixed.
… wipe Review of devsuitup#486: an open or saved SDK session vanished from the list, a continuation typed in a compaction mirror kept its parent hidden, and the entrypoint column wiped session_cache from db.js, before the single-instance lock. The column now lands as NULL (visible) and a chunked backfill fills it from each transcript's head; a cheap entrypoint scan replaces the full re-read of cached SDK rows; an SDK-only project no longer keeps an empty header; non-string entrypoints are stored as none. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
An SDK prompt is written first as a queue-operation line that can exceed the 256 KB head on its own, so the backfill left ~190 SDK sessions visible. Read the head in chunks until the first user turn. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
|
@devsuitup thanks for the review. Every point is addressed in 732628f and 7f1dd0d. CI is green on 7f1dd0d, and an internal re-review run against your list found nothing left. Blocking
Non-blocking
The context doc ( |
devsuitup
left a comment
There was a problem hiding this comment.
Review at 7f1dd0d. The last commit (the first user turn past an oversized prompt) only touches read-session-file.js and its test, so the findings below were traced at 732628f and still hold: session-cache.js and public/app.js are unchanged by it. Thanks for the rework: the entrypoint column is now added without a wipe, a pure SDK transcript stays hidden while a later interactive turn makes the parent visible, and the failure case no longer leaves an empty cache. 287 tests in 32 related files pass locally on Node 24. Two points still block; the first is the part of the earlier blocker that remains.
Blocking
- A partial cold restore overwrites the saved working set before pending SDK sessions are indexed (
session-cache.jshiddenSdkSessionIds~477,public/app.jspersistWorkingSet~194). The exemption readsopenWorkingSetfrom the saved settings.persistWorkingSetrewrites that list with only the open sessions and the skipped entries, so a saved session the restore has not reached yet is dropped from it. Persisting once during a cold restore (any click or close) therefore removes the exemption of every still-pending SDK session: it becomes hidden, and the next restore cannot find it. Fix: keep the saved entries that are not yet resolved when persisting (the same shape of problem as the one in #441), or take the exemption from the live state instead of the saved list. Test: persist while a saved SDK entry is still unindexed, then check that it is still exempt. - Attaching a hidden SDK session after the list was built does not refresh the sidebar (
session-cache.js~478,public/app.jsopenSession~1365).activeSessionsis read when the list is built; opening an SDK session afterwards (a resume from search, a deep link, a trigger) adds no reload, so the attached terminal stays absent from the sidebar and can be left out when the grid is rebuilt. Fix: reload the projects, or notify the renderer, when a session that the filter hides becomes active. Test: hide it, open it, and check that the next list contains it.
Non-blocking
- Files above 2 MiB miss terminal turns outside the final 256 KiB, including turns already inside the inspected head (
read-session-file.js~643); worth a follow-up. - Deleting the backfill invocation leaves
test/get-projects-cold-start-reconcile.test.jsgreen, and no test combines the restore with the open timing. test/hide-sdk-sessions.test.jscarries rationale comments beyond the one-line pointer allowed in code.
Not run: Electron-based migration and native DB tests, a large-cache benchmark, Linux Node 20/22 with c8, lint, fresh CI. CI on this head is not approved yet; I will approve it once the blocking points are fixed.
…e opened Second review of devsuitup#486: a persist during a cold restore rewrote openWorkingSet without the entries not reached yet, dropping their SDK exemption; keep the planner's pending entries and the ones awaiting the restore toast. Opening a hidden SDK session after the list was built left it out of the sidebar; notify the renderer when it becomes active. Scan the head of large SDK transcripts as well as their tail, and pin the backfill call in get-projects with a test. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
|
@devsuitup thanks. Both blocking points from the review at 7f1dd0d are addressed in 89e4604. CI is green, and an internal re-review of that commit found nothing. Blocking
Non-blocking
|
devsuitup
left a comment
There was a problem hiding this comment.
Review at 89e4604. Thanks: pending restore entries are now kept when the working set is persisted, and opening a hidden SDK session notifies the renderer (the reviewer checked the wiring and the existing assertions were preserved, not weakened). 199 tests in 27 related files pass locally on Node 24, and there is no conflict with main. One point still blocks, and it sits in the gap the new pendingRestoreEntries leaves.
Blocking
- A restore candidate is in neither list while its check is pending (
public/app.js,persistWorkingSet~189,pendingRestoreEntries~202,runRestore). An entry leavesrestorePlanner.pending()when it is handed torunRestore, but it is only inopenSessionsorskippedWorkingSetEntriesafterliveElsewhereManyand the open have finished. If a persist runs in that window (or a second restore overlaps the first), the entry is dropped fromopenWorkingSet. An SDK candidate then loses its exemption, a filtered reload hides it, and the restore silently skips it. The reviewer reproduced it with the shipped functions. Fix: keep a set of the entries dispatched torunRestoreand not yet decided, and include it inheld; clear each entry when it is opened or skipped. Test: persist during theliveElsewhereManyawait, and with two overlapping restores, then check that the SDK entry is still stored.
Non-blocking
- Transcripts above 2 MiB still miss interactive turns between the sampled head and tail (
read-session-file.js~661). - Deleting the production reveal calls leaves the helper test green (
test/hide-sdk-sessions.test.js~217): assert the call through the opening path.
Not run: Electron migration and native DB tests, Linux Node 20/22 with c8, lint, fresh CI. CI on this head is not approved yet; I will approve it once this point is fixed.
Third review of devsuitup#486: an entry handed to runRestore left the planner's pending list but only reached openSessions or the skipped list once its live-elsewhere check and open were done, so a persist in between (or an overlapping restore) dropped it. Track those entries in restoreInFlight. Also read large SDK transcripts in full during the one-time backfill, and pin the production reveal calls with a test. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
An entry opened by hand or deleted during the live-elsewhere check left restoreInFlight through no path, so a session the user then closed was put back into the working set on every persist. Also read main.js with LF line endings in the reveal wiring test, which failed on Windows checkouts. Claude-Session: https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q
|
@devsuitup thanks. The blocking point from the review at 89e4604 is addressed in 3dd7fbc and 2b46e28. CI is green on 2b46e28, Windows included, and an internal re-review found nothing left. Blocking: a restore candidate was in no list while its check was pending.
So a session the user opened during the check and then closed is not written back. In
Non-blocking
|
devsuitup
left a comment
There was a problem hiding this comment.
Review at 2b46e28. The blocker of 89e4604 is fixed: restore candidates are kept in the stored set while they are being opened, and cleared on every exit of runRestore. The reviewer reproduced the earlier scenario with the shipped functions and it no longer drops the entry; the new regression assertions fail against 89e4604. Existing presence and refresh assertions were preserved, the changelog still ends (#486), and there is no conflict with main. 186 tests in the related files pass locally on Node 24.
Non-blocking
- With two direct restores of the same id, the first rejection deletes the second call's tracking entry and a persist then drops it (
public/app.js~251). The planner does not dispatch an id twice and the presence helper catches IPC errors, so I found no production route; keying the entries by call rather than by id would close it. - Deleting the outer cleanup in
runRestoreleaves the six tests intest/restore-pending-persist.test.jsgreen: the failure test has one candidate and the overlap test releases both checks together. A case where the first call rejects while the second is pending would pin it. - The full backfill can scan up to 200 large transcripts synchronously before yielding (
session-cache.js~457), and it now detects interactive turns beyond 2 MiB. Startup latency was not measured; yielding between files would keep it bounded.
Not run: Electron migration and native DB tests, Linux Node 20/22 with c8, lint, a large-cache benchmark, fresh CI.
Problem
Programs that drive Claude through the Agent SDK write ordinary top-level transcripts in the project folder. Examples are a Python review tool that starts one session per batch of files, the brain-runner's
claude -pepisodes, and strap's developers. These transcripts haveisSidechain: false, nosubagents/directory and no record pointing back at the session that started them. Ininternal/runnerthey buried the real sessions under dozens of "Security review of …" rows.The only reliable marker is the
entrypointfield the CLI writes on every record:cliwhen typed in a terminal,sdk-cli/sdk-py/sdk-tswhen launched through the SDK. On one machine that is about 4,500 SDK transcripts against about 540 interactive ones.Change
readSessionFilekeeps theentrypointof the firsttype: 'user'record, and it is stored in a newsession_cache.entrypointcolumn. The column is added through schema reconciliation withmustReindex, so the cache is rebuilt once.buildProjectsFromCacheleavessdk-*rows out of the project list while the new globalhideSdkSessionssetting is on (the default). The setting has a toggle in Global Settings; changing it reloads the project list.createScheduleSessionwithout anentrypoint, and only the laterclaude --resume -precords saysdk-cli.session_cache,session_metricsand FTS, so the heatmap and token totals still count that activity.Documentation:
.ai/contexts/session-cache.md("SDK-launched sessions"),docs/settings.mdanddocs/session-browser.md.Tests
test/hide-sdk-sessions.test.jscovers how the entrypoint is read, a scheduled run, the hidden-by-default case and the setting turned off.task check: 0 errors, 0 failing tests.https://claude.ai/code/session_0121XfXy4y1Fwt3wJsD4v82q