Surface sessions that have no tasks directory - #29
Open
dgruhin-hrizn wants to merge 3 commits into
Open
Conversation
/api/sessions enumerates only TASKS_DIR. The comment above that loop says "First, add sessions that have tasks directories" -- but the second pass it implies was never written, so a session that never wrote a todo is unreachable through any endpoint. Claude Code only creates a tasks directory once a session actually writes one, so this hides ordinary conversations. On this machine that is 30 of 37 sessions invisible. Reported in L1AD#18. Strong hint the second pass was always intended: fetchTasks already handles the 404 for a missing tasks directory, with a comment reading "Session has no tasks directory yet." Clicking one of these sessions already worked -- there was just no way to reach it. Server: after the existing loop, add sessions known only from the transcripts in PROJECTS_DIR, with taskCount 0 and hasTasks false. modifiedAt comes from the transcript's mtime rather than a directory's. The existing task-less fallback uses directory mtime, which Claude Code bumps when it removes its .lock at session end -- that timestamp reflects teardown rather than activity, so it is not repeated here. Client: these render "No tasks" instead of a 0/0 progress bar, which otherwise reads as "nothing done yet" rather than "never had any". Two interactions worth stating: The "Active Only" filter needs pending > 0 || inProgress > 0, and these sessions are all zero, so they were already excluded -- that filter is unchanged. Most are also older than the 7-day threshold, so they land in the collapsed Archived disclosure rather than the main list. The default limit of 20 is the real risk. Applied naively, 30 new sessions compete for those slots and the default view drops from 7 task-bearing sessions to 6 -- a regression for anyone who only cares about sessions with tasks. So the new "With Tasks" option is applied server-side, before the limit, rather than client-side after it: ?limit=20&withTasks=1 returns 7 of 7. Filtering after limiting would quietly turn "20 most recent" into "however many of the 20 most recent happen to match". filterBySessions now refetches rather than only re-rendering, since that filter is no longer purely client-side. The client-side filter is kept as a no-op safety net so the option still behaves against an older server.
Surfacing task-less sessions made them eligible for the boot auto-select, which picks sessions[0]. Since that list is sorted newest-first and a conversation that never wrote a todo is often the most recent thing you did, the app frequently opened to an empty board -- and then 404'd /api/sessions/:id on every SSE refresh, because there is no tasks directory to read. The 404 is handled and harmless, but an empty board is a poor landing view and the repeated request is noise. Picks the newest session with hasTasks !== false, falling back to sessions[0] so behaviour is unchanged when nothing has tasks. Task-less sessions remain listed and selectable; they are just not the default. (cherry picked from commit ac6ac43)
Surfacing sessions that never wrote a todo is right -- they were unreachable -- but they outnumber the rest roughly 4:1 here (30 of 37), so as a default they bury the thing people open the app to look at. Defaulting to "With Tasks" means the out-of-the-box sidebar is exactly what it was before those sessions became visible, and seeing everything is one click away rather than the other way round. Only the default changes. sessionFilter is written to localStorage solely by filterBySessions, i.e. only when the user picks something, so anyone who has explicitly chosen "All Sessions" keeps it. Verified both paths: a first run with no stored value lands on with-tasks showing 7 sessions and no implicit write, and a stored 'all' still loads as 'all' showing 20. Also adds the matching empty-state hint, which previously had no branch for this filter and would have shown a bare "No sessions". (cherry picked from commit 0832851)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18.
The bug
/api/sessionsenumerates onlyTASKS_DIR. The comment above that loop reads:// First, add sessions that have tasks directories…but the second pass it implies was never written. Claude Code only creates a tasks directory once a session actually writes a todo, so any ordinary conversation that never used one is unreachable through any endpoint — no limit, filter, or sort will surface it.
On my machine that's 30 of 37 sessions invisible.
A strong hint this was always intended:
fetchTasksalready handles the 404 for a missing tasks directory, with a comment reading "Session has no tasks directory yet." Clicking one of these sessions already worked — there was simply no way to reach it.The fix
Server — after the existing loop, add sessions known only from the transcripts in
PROJECTS_DIR, withtaskCount: 0andhasTasks: false.modifiedAtcomes from the transcript's mtime rather than a directory's. Note the existing task-less fallback uses directory mtime, which Claude Code bumps when it removes its.lockat session end — so that timestamp reflects teardown rather than activity. I didn't repeat that here, and haven't changed the existing fallback in this PR.Client — these render
No tasksinstead of a0/0progress bar, which otherwise reads as "nothing done yet" rather than "never had any".Interactions, all checked
pending > 0 || inProgress > 0, and these sessions are all zero, so they were already excluded.So the new "With Tasks" filter is applied server-side, before the limit, rather than client-side after it:
Filtering after limiting would quietly turn "20 most recent" into "however many of the 20 most recent happen to match".
filterBySessionsnow refetches rather than only re-rendering, since that filter is no longer purely client-side; the client-side filter is kept as a no-op safety net so the option still behaves against an older server.This is non-breaking by default
"With Tasks" is the default, so the out-of-the-box sidebar is byte-for-byte the view you have today — the 30 newly-reachable sessions are one click away rather than in your face. On my machine they outnumber the rest 30:7, and as a default they bury the thing you opened the app to look at.
Only the default changes.
sessionFilteris written to localStorage solely byfilterBySessions— i.e. only when a user picks something — so anyone who has explicitly chosen "All Sessions" keeps it. Verified both: a first run with no stored value lands onwith-taskswith no implicit write, and a stored'all'still loads as'all'.If you'd rather ship it the other way round, flipping the default is a one-line change.
Note on ordering
If you take #28 as well, that one keeps its short-id aliases in a separate cache specifically so this second pass — which iterates
Object.entries(metadata)— doesn't manufacture phantom duplicate sessions from alias keys. They're order-independent either way.