Bug Description
Desktop/TUI sessions that have an explicit workspace can move into the No workspace group after context compression. The compression continuation row is created without inheriting the parent/root cwd, and the session list projection then surfaces the continuation/tip cwd (null) in place of the root cwd.
This came from a local audit of a real state.db using read-only SQLite access. Private local paths and user/client content are intentionally omitted/redacted.
Root Cause Evidence
Desktop groups by session.cwd:
// apps/desktop/src/app/chat/sidebar/index.tsx
const path = session.cwd?.trim() || ''
const id = path || '__no_workspace__'
const label = baseName(path) || path || noWorkspaceLabel
TUI intentionally persists cwd = NULL when no workspace was explicitly selected:
# tui_gateway/server.py
db.create_session(
key,
source="tui",
model=_resolve_model(),
cwd=_session_cwd(session) if session.get("explicit_cwd") else None,
)
That behavior appears intentional and is covered by tests/test_tui_gateway_server.py::test_ensure_session_db_row_defaults_to_no_workspace.
The bug is in compression rotation. The continuation session is created without passing cwd:
# agent/conversation_compression.py
agent._session_db.create_session(
session_id=agent.session_id,
source=agent.platform or os.environ.get("HERMES_SESSION_SOURCE", "cli"),
model=agent.model,
model_config=agent._session_init_model_config,
parent_session_id=old_session_id,
)
Then list_sessions_rich() projects compression roots to their tip and copies cwd from the tip:
# hermes_state.py
for key in (
"id", "ended_at", "end_reason", "message_count",
"tool_call_count", "title", "last_active", "preview",
"model", "system_prompt", "cwd",
):
if key in tip_row:
merged[key] = tip_row[key]
So a root row with a valid workspace cwd can be projected as a tip row with cwd = NULL, which Desktop correctly renders under No workspace.
Local Evidence
A read-only query of local state.db found this shape:
- compression root row:
cwd = <explicit workspace path>
- compression continuation/tip row:
cwd = NULL
- projected sidebar-like result: the live/tip session lands under No workspace
The same query also found other No-workspace sessions whose root rows already had cwd = NULL; those match the intentional no-explicit-workspace behavior and are not this bug.
Expected Behavior
A compressed continuation of a workspaced conversation should keep the workspace binding. A session with an explicit workspace should not move to No workspace merely because context compression rotated the session id.
Actual Behavior
Compression continuation rows are created with null cwd; list_sessions_rich() then copies that null cwd into the projected visible row.
Suggested Fix
In agent/conversation_compression.py, capture the old session cwd before rotating and pass it to the continuation create_session(...):
old_row = agent._session_db.get_session(agent.session_id)
old_cwd = old_row.get("cwd") if old_row else None
...
agent._session_db.create_session(
session_id=agent.session_id,
source=agent.platform or os.environ.get("HERMES_SESSION_SOURCE", "cli"),
model=agent.model,
model_config=agent._session_init_model_config,
parent_session_id=old_session_id,
cwd=old_cwd,
)
An alternative/additional guard is for list_sessions_rich() projection to preserve/fallback to root cwd when the tip cwd is null.
Regression Tests
- Compression continuation inherits parent/root
cwd.
list_sessions_rich() projection does not move a workspaced compressed conversation into No workspace.
- Existing intentional behavior remains: TUI sessions without an explicit workspace persist
cwd = NULL.
Related
This appears related to open PR #41678, whose summary mentions preserving workspace cwd fallback for compacted Desktop sessions.
Bug Description
Desktop/TUI sessions that have an explicit workspace can move into the No workspace group after context compression. The compression continuation row is created without inheriting the parent/root
cwd, and the session list projection then surfaces the continuation/tipcwd(null) in place of the rootcwd.This came from a local audit of a real
state.dbusing read-only SQLite access. Private local paths and user/client content are intentionally omitted/redacted.Root Cause Evidence
Desktop groups by
session.cwd:TUI intentionally persists
cwd = NULLwhen no workspace was explicitly selected:That behavior appears intentional and is covered by
tests/test_tui_gateway_server.py::test_ensure_session_db_row_defaults_to_no_workspace.The bug is in compression rotation. The continuation session is created without passing
cwd:Then
list_sessions_rich()projects compression roots to their tip and copiescwdfrom the tip:So a root row with a valid workspace
cwdcan be projected as a tip row withcwd = NULL, which Desktop correctly renders under No workspace.Local Evidence
A read-only query of local
state.dbfound this shape:cwd = <explicit workspace path>cwd = NULLThe same query also found other No-workspace sessions whose root rows already had
cwd = NULL; those match the intentional no-explicit-workspace behavior and are not this bug.Expected Behavior
A compressed continuation of a workspaced conversation should keep the workspace binding. A session with an explicit workspace should not move to No workspace merely because context compression rotated the session id.
Actual Behavior
Compression continuation rows are created with null
cwd;list_sessions_rich()then copies that nullcwdinto the projected visible row.Suggested Fix
In
agent/conversation_compression.py, capture the old sessioncwdbefore rotating and pass it to the continuationcreate_session(...):An alternative/additional guard is for
list_sessions_rich()projection to preserve/fallback to rootcwdwhen the tipcwdis null.Regression Tests
cwd.list_sessions_rich()projection does not move a workspaced compressed conversation into No workspace.cwd = NULL.Related
This appears related to open PR #41678, whose summary mentions preserving workspace cwd fallback for compacted Desktop sessions.