Skip to content

Desktop/TUI compressed sessions can move into No workspace because continuation cwd is null #42228

Description

@MatheusBazarinRibeiro

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

  1. Compression continuation inherits parent/root cwd.
  2. list_sessions_rich() projection does not move a workspaced compressed conversation into No workspace.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havecomp/tuiTerminal UI (ui-tui/ + tui_gateway/)type/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions