fix(state): inherit cwd/git_repo_root on parent_session_id children

_insert_session_row never copied cwd/git_repo_root from a parent row when
parent_session_id was set, and git_repo_root wasn't even in the INSERT's
column list. The compression-fork path (and delegate/subagent spawns,
branch continuations) creates a child session without passing cwd/
git_repo_root at all, so the child's tip is born NULL — and since the
Desktop project sidebar groups sessions by cwd, the whole project silently
drops out of the sidebar every time a long conversation compresses. A
lineage that compresses repeatedly compounds this across generations.

Add git_repo_root to _insert_session_row's INSERT/COALESCE-on-conflict
column set, and backfill both cwd and git_repo_root from the immediate
parent row (single non-recursive hop, matching the existing COALESCE
"never overwrite an explicit value" contract) inside the same write
transaction whenever parent_session_id is set. A multi-generation chain
resolves correctly because each generation's own create_session call
already backfills from its (already-resolved) immediate parent.

Fixes #64709
This commit is contained in:
Simplicio, Wesley (ext)
2026-07-15 00:22:39 -03:00
committed by Teknium
parent b6a2b701c1
commit 3c74c12554
2 changed files with 70 additions and 3 deletions
+29 -3
View File
@@ -2709,6 +2709,7 @@ class SessionDB:
parent_session_id: str = None,
cwd: str = None,
profile_name: str = None,
git_repo_root: str = None,
) -> None:
"""Insert a session row, enriching NULL metadata on conflict.
@@ -2727,14 +2728,25 @@ class SessionDB:
a persisted, now-inactive row belongs to the caller's chat/thread before
switching to it (IDOR scoping — without them the ``sessions`` table has
no chat/thread to compare).
When ``parent_session_id`` is set (compression fork, delegate/subagent
spawn, branch continuation) and this row's own ``cwd``/``git_repo_root``
are still NULL after the insert, they are backfilled from the parent
row. Callers of ``create_session`` for a child session historically
didn't propagate these fields themselves (e.g. the compression-fork
path), so a lineage could silently lose its working directory and drop
out of the project sidebar every time it forked (#64709). This only
fills NULLs — an explicit ``cwd``/``git_repo_root`` on the child is
never overwritten.
"""
def _do(conn):
conn.execute(
"""INSERT INTO sessions (
id, source, user_id, session_key, chat_id, chat_type, thread_id,
model, model_config, system_prompt, parent_session_id, cwd, profile_name, started_at
model, model_config, system_prompt, parent_session_id, cwd,
profile_name, git_repo_root, started_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(id) DO UPDATE SET
model = COALESCE(sessions.model, excluded.model),
model_config = COALESCE(sessions.model_config, excluded.model_config),
@@ -2745,7 +2757,8 @@ class SessionDB:
thread_id = COALESCE(sessions.thread_id, excluded.thread_id),
parent_session_id = COALESCE(sessions.parent_session_id, excluded.parent_session_id),
cwd = COALESCE(sessions.cwd, excluded.cwd),
profile_name = COALESCE(sessions.profile_name, excluded.profile_name)""",
profile_name = COALESCE(sessions.profile_name, excluded.profile_name),
git_repo_root = COALESCE(sessions.git_repo_root, excluded.git_repo_root)""",
(
session_id,
source,
@@ -2760,9 +2773,22 @@ class SessionDB:
parent_session_id,
cwd,
profile_name,
git_repo_root,
time.time(),
),
)
if parent_session_id:
conn.execute(
"""UPDATE sessions
SET cwd = COALESCE(sessions.cwd,
(SELECT p.cwd FROM sessions p
WHERE p.id = sessions.parent_session_id)),
git_repo_root = COALESCE(sessions.git_repo_root,
(SELECT p.git_repo_root FROM sessions p
WHERE p.id = sessions.parent_session_id))
WHERE id = ? AND parent_session_id IS NOT NULL""",
(session_id,),
)
self._execute_write(_do)
def create_session(self, session_id: str, source: str, **kwargs) -> str: