Skip to content

fix: Filter worktree child sessions from web UI sidebar#382

Open
EEmayank wants to merge 1 commit intoRunMaestro:mainfrom
EEmayank:agentfilter
Open

fix: Filter worktree child sessions from web UI sidebar#382
EEmayank wants to merge 1 commit intoRunMaestro:mainfrom
EEmayank:agentfilter

Conversation

@EEmayank
Copy link
Contributor

@EEmayank EEmayank commented Feb 15, 2026

fix: Filter worktree child sessions from web UI sidebar

Problem

The web UI sidebar displayed significantly more agents than the Electron desktop app. Every worktree child session (branches spawned from a parent agent's worktree config) appeared as a top-level item in the sidebar, inflating the visible agent count. For example, a parent agent with 2 worktree children showed as 3 separate entries in the web sidebar, but only 1 (with expandable children) in the desktop app.

Root Cause

The Electron desktop sidebar in SessionList.tsx filters out sessions where parentSessionId is set — worktree children are excluded from the main list and rendered as nested sub-items under their parent. The web sidebar had no equivalent filtering: groupSessions() in sessionGrouping.ts passed every session through to the rendered output without checking for parent-child relationships.

Additionally, the server-side SessionData type in types.ts did not declare parentSessionId or worktreeBranch, even though the factory was already serializing and sending those fields to web clients — a type-safety gap.

Fix

src/main/web-server/types.ts

  • Added parentSessionId?: string | null and worktreeBranch?: string | null to the SessionData interface, aligning the type definition with the data already being sent by the server factory.

src/web/utils/sessionGrouping.ts

  • Added a topLevelSessions filter at the top of groupSessions() that excludes sessions with a parentSessionId, matching the Electron sidebar behavior.
  • Both the bookmarks extraction and the group-assignment loop now operate on topLevelSessions instead of the unfiltered input.

Summary by CodeRabbit

  • New Features
    • Sessions now support hierarchical organization with parent-child relationships.
    • Added session grouping and filtering capabilities to better organize and discover work sessions.
    • Session display names now adapt based on their organizational context.

Copy link
Collaborator

@pedramamini pedramamini left a comment

Choose a reason for hiding this comment

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

PR Review: Filter worktree child sessions from web UI sidebar

Critical: sessionGrouping.ts is dead code — nothing imports it

The new utility module (src/web/utils/sessionGrouping.ts) is created but never imported anywhere. Grepping for sessionGrouping across the entire src/ tree returns zero matches. The module's docstring claims it's "Used by Sidebar, AllSessionsView, and SessionPillBar" — but none of those components import or use it.

The PR doesn't actually fix the bug. The filtering logic exists but is disconnected.

Major: Duplicate code with AllSessionsView.tsx

AllSessionsView.tsx (lines 200-295) already contains identical inline implementations of:

  • findParentSession() (with extra debug console.log statements)
  • getSessionDisplayName()
  • getSessionEffectiveGroup()

The new sessionGrouping.ts is a cleaner copy of the same functions. The right approach: create the shared module and refactor AllSessionsView.tsx to import from it, deleting the inline copies. This also removes the debug console.log sprinkled throughout the AllSessionsView.tsx versions.

Minor: SessionPillBar.tsx doesn't filter either

SessionPillBar.tsx renders sessions without any parentSessionId filtering. If the utility module is meant to be shared across all web views, it should also be wired into the pill bar.

Valid: SessionData type change

The parentSessionId/worktreeBranch fields exist on SessionBroadcastData but were missing from SessionData. This addition aligns the two types — good change.


Suggested action items

  1. Wire up imports — Import groupSessions, filterSessions, getSessionDisplayName into the web sidebar/views that actually render session lists
  2. Refactor AllSessionsView.tsx — Replace inline findParentSession/getSessionDisplayName/getSessionEffectiveGroup with imports from the shared module (also removes debug console.log spam)
  3. Filter in SessionPillBar.tsx — Apply parentSessionId filter so worktree children don't appear as top-level pills
  4. Add a test for groupSessions() confirming worktree children are excluded from top-level listing

@pedramamini
Copy link
Collaborator

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

The changes extend the SessionData interface with two new optional fields (parentSessionId and worktreeBranch) to support parent-child session relationships, and introduce a new utility module for organizing sessions into hierarchical groups with filtering by multiple session attributes.

Changes

Cohort / File(s) Summary
Type Extension
src/main/web-server/types.ts
Added optional fields parentSessionId and worktreeBranch to SessionData interface to support session hierarchy and worktree tracking.
Session Grouping Utilities
src/web/utils/sessionGrouping.ts
New module providing functions to find parent sessions, compute display names and effective groups, group sessions hierarchically with sorting, and filter sessions by name, path, tool type, and worktree branch.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: filtering out worktree child sessions from the web UI sidebar to match Electron behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/web/utils/sessionGrouping.ts`:
- Around line 138-154: filterSessions currently only checks fields on each
session row (using getSessionDisplayName and
session.name/cwd/toolType/worktreeBranch) but because groupSessions removes
child rows a query that only matches a child (e.g., child's worktreeBranch) will
exclude the parent and return no results; fix by extending filterSessions to
also consider children: for each parent session being tested, look up its child
sessions from allSessions (e.g., by parent id relationship used in
groupSessions) and treat the parent as a match if any child’s displayName, name,
cwd, toolType, or worktreeBranch matches the query (i.e., "promote" a matching
child to include its parent in results); keep all existing checks (including
getSessionDisplayName) and short-circuit when query is empty.
- Around line 19-33: The inferred basePath from the worktree regex may include a
trailing path separator which breaks equality and startsWith checks; normalize
basePath (e.g., strip any trailing '/' or '\\' or run through path
normalization) into a new variable like basePathNormalized and use that in the
sessions.find comparisons (replace uses of basePath with basePathNormalized and
build startsWith checks with basePathNormalized + '/' and basePathNormalized +
'\\') so parent inference works reliably across path layouts.

Comment on lines +19 to +33
// Try to infer parent from worktree path patterns
const cwd = session.cwd;
const worktreeMatch = cwd.match(/^(.+?)[-]?WorkTrees[\/\\]([^\/\\]+)/i);

if (worktreeMatch) {
const basePath = worktreeMatch[1];
return (
sessions.find(
(s) =>
s.id !== session.id &&
!s.parentSessionId &&
(s.cwd === basePath ||
s.cwd.startsWith(basePath + '/') ||
s.cwd.startsWith(basePath + '\\'))
) || null
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Normalize inferred basePath before comparisons.
The regex capture can include a trailing separator (e.g., .../WorkTrees/...), which makes the equality and startsWith checks miss the parent path. This breaks parent inference in common path layouts.

🛠️ Proposed fix
-	const basePath = worktreeMatch[1];
+	const basePath = worktreeMatch[1].replace(/[\/\\]+$/, '');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Try to infer parent from worktree path patterns
const cwd = session.cwd;
const worktreeMatch = cwd.match(/^(.+?)[-]?WorkTrees[\/\\]([^\/\\]+)/i);
if (worktreeMatch) {
const basePath = worktreeMatch[1];
return (
sessions.find(
(s) =>
s.id !== session.id &&
!s.parentSessionId &&
(s.cwd === basePath ||
s.cwd.startsWith(basePath + '/') ||
s.cwd.startsWith(basePath + '\\'))
) || null
// Try to infer parent from worktree path patterns
const cwd = session.cwd;
const worktreeMatch = cwd.match(/^(.+?)[-]?WorkTrees[\/\\]([^\/\\]+)/i);
if (worktreeMatch) {
const basePath = worktreeMatch[1].replace(/[\/\\]+$/, '');
return (
sessions.find(
(s) =>
s.id !== session.id &&
!s.parentSessionId &&
(s.cwd === basePath ||
s.cwd.startsWith(basePath + '/') ||
s.cwd.startsWith(basePath + '\\'))
) || null
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/utils/sessionGrouping.ts` around lines 19 - 33, The inferred basePath
from the worktree regex may include a trailing path separator which breaks
equality and startsWith checks; normalize basePath (e.g., strip any trailing '/'
or '\\' or run through path normalization) into a new variable like
basePathNormalized and use that in the sessions.find comparisons (replace uses
of basePath with basePathNormalized and build startsWith checks with
basePathNormalized + '/' and basePathNormalized + '\\') so parent inference
works reliably across path layouts.

Comment on lines +138 to +154
export function filterSessions(
sessions: Session[],
query: string,
allSessions: Session[]
): Session[] {
if (!query.trim()) return sessions;
const q = query.toLowerCase();
return sessions.filter((session) => {
const displayName = getSessionDisplayName(session, allSessions);
return (
displayName.toLowerCase().includes(q) ||
session.name.toLowerCase().includes(q) ||
session.cwd.toLowerCase().includes(q) ||
(session.toolType && session.toolType.toLowerCase().includes(q)) ||
(session.worktreeBranch && session.worktreeBranch.toLowerCase().includes(q))
);
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Search can drop matches that only exist in child sessions.
Because groupSessions() removes children, a query that matches only a worktree child (e.g., by worktreeBranch) yields an empty UI result. Consider promoting a matching child to its parent (or otherwise ensuring the parent appears) so search remains useful.

🛠️ Proposed fix (promote matching child to parent)
 export function filterSessions(
 	sessions: Session[],
 	query: string,
 	allSessions: Session[]
 ): Session[] {
 	if (!query.trim()) return sessions;
 	const q = query.toLowerCase();
-	return sessions.filter((session) => {
-		const displayName = getSessionDisplayName(session, allSessions);
-		return (
-			displayName.toLowerCase().includes(q) ||
-			session.name.toLowerCase().includes(q) ||
-			session.cwd.toLowerCase().includes(q) ||
-			(session.toolType && session.toolType.toLowerCase().includes(q)) ||
-			(session.worktreeBranch && session.worktreeBranch.toLowerCase().includes(q))
-		);
-	});
+	const results = new Map<string, Session>();
+	for (const session of sessions) {
+		const displayName = getSessionDisplayName(session, allSessions);
+		const matches =
+			displayName.toLowerCase().includes(q) ||
+			session.name.toLowerCase().includes(q) ||
+			session.cwd.toLowerCase().includes(q) ||
+			(session.toolType && session.toolType.toLowerCase().includes(q)) ||
+			(session.worktreeBranch && session.worktreeBranch.toLowerCase().includes(q));
+		if (matches) {
+			const parent = findParentSession(session, allSessions);
+			const effective = parent ?? session;
+			results.set(effective.id, effective);
+		}
+	}
+	return Array.from(results.values());
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/web/utils/sessionGrouping.ts` around lines 138 - 154, filterSessions
currently only checks fields on each session row (using getSessionDisplayName
and session.name/cwd/toolType/worktreeBranch) but because groupSessions removes
child rows a query that only matches a child (e.g., child's worktreeBranch) will
exclude the parent and return no results; fix by extending filterSessions to
also consider children: for each parent session being tested, look up its child
sessions from allSessions (e.g., by parent id relationship used in
groupSessions) and treat the parent as a match if any child’s displayName, name,
cwd, toolType, or worktreeBranch matches the query (i.e., "promote" a matching
child to include its parent in results); keep all existing checks (including
getSessionDisplayName) and short-circuit when query is empty.

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.

2 participants

Comments