Skip to content

Commit 9e58019

Browse files
Nickclaude
andcommitted
Polish per review: clarify doc comment, add null-safe parent test
Update the LinkSubagentSessions doc comment to cover the re-parenting of nested subagents (it previously described only the type upgrade). Add TestLinkSubagentSessionsLinksNullParentSubagent: a 'subagent'-tagged session with a NULL parent plus a spawn edge must be linked to its spawner. The test fails if the null-safe `IS NOT` is replaced with `!=` (mutation-verified), and was suggested independently by two reviewers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9d2e0b6 commit 9e58019

2 files changed

Lines changed: 49 additions & 5 deletions

File tree

internal/db/link_subagent_nested_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,43 @@ func TestLinkSubagentSessionsUpgradesTypeWhenParentAlreadyMatches(t *testing.T)
142142
"child.parent_session_id")
143143
}
144144
}
145+
146+
// TestLinkSubagentSessionsLinksNullParentSubagent guards the null-safe `IS NOT`
147+
// predicate. A session already tagged 'subagent' but with a NULL parent (and a
148+
// tool_calls spawn edge) must be linked to its spawner. Replacing `IS NOT` with
149+
// `!=` would leave the parent NULL (`NULL != 'x'` is NULL, not true), so this
150+
// test fails under that mutation.
151+
func TestLinkSubagentSessionsLinksNullParentSubagent(t *testing.T) {
152+
d := testDB(t)
153+
154+
insertSession(t, d, "spawner", "p", func(s *Session) {
155+
s.MessageCount = 1
156+
})
157+
158+
// Already tagged 'subagent' (so the type branch is false) but its parent
159+
// was never set. Only the null-safe parent branch can link it.
160+
insertSession(t, d, "orphan", "p", func(s *Session) {
161+
s.MessageCount = 1
162+
s.RelationshipType = "subagent"
163+
// ParentSessionID left nil -> NULL in the DB.
164+
})
165+
166+
insertMessages(t, d, Message{
167+
SessionID: "spawner", Ordinal: 0, Role: "assistant",
168+
Content: "spawn orphan", HasToolUse: true,
169+
ToolCalls: []ToolCall{{
170+
ToolName: "Agent", Category: "Task",
171+
SubagentSessionID: "orphan",
172+
}},
173+
})
174+
175+
require.NoError(t, d.LinkSubagentSessions(), "LinkSubagentSessions")
176+
177+
orphan, err := d.GetSession(context.Background(), "orphan")
178+
requireNoError(t, err, "GetSession orphan")
179+
if assert.NotNil(t, orphan.ParentSessionID,
180+
"NULL-parent subagent must be linked to its spawner (null-safe IS NOT)") {
181+
assert.Equal(t, "spawner", *orphan.ParentSessionID,
182+
"orphan.parent_session_id")
183+
}
184+
}

internal/db/sessions.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1544,11 +1544,15 @@ func (db *DB) GetChildSessions(
15441544
}
15451545

15461546
// LinkSubagentSessions sets parent_session_id and
1547-
// relationship_type on sessions that are referenced by
1548-
// tool_calls.subagent_session_id. Updates sessions that either
1549-
// have no parent yet or have a non-subagent relationship (e.g.
1550-
// a Zencoder session classified as "continuation" from header
1551-
// parentId that is actually a spawned subagent).
1547+
// relationship_type on sessions referenced by
1548+
// tool_calls.subagent_session_id (the authoritative spawn edge).
1549+
// A session is updated when it is not yet tagged 'subagent' (e.g.
1550+
// a Zencoder session classified as "continuation" from a header
1551+
// parentId that is actually a spawned subagent) OR when its stored
1552+
// parent disagrees with the spawn edge. The latter re-parents
1553+
// nested subagents (depth >= 2), which the parser pins to the main
1554+
// session because Claude Code stores every subagent flat under
1555+
// <main>/subagents/. Already-correct subagents are left untouched.
15521556
func (db *DB) LinkSubagentSessions() error {
15531557
db.mu.Lock()
15541558
defer db.mu.Unlock()

0 commit comments

Comments
 (0)