Repository navigation
feat(sessions): session liveness + UI autostart on session start (2.128.0) - #171
Merged
Merged
Conversation
…28.0) Starting an IDE session left you with no project UI "in some cases", and the cases were not random: nothing in C3 connected a session starting to a UI server running. launch_session had five callers — none in the MCP or hook path — and the hub's autostart thread fired ONCE, two seconds after startup, over autostart_ui projects (true on 0 of 60 registered here). A UI that was there had been left behind by an earlier session. Liveness had the same shape of problem: it was inferred from activity rows, ended only by a hub "end session" click or 20 minutes of tool-call silence. The session_end row added in 2.126.0 was read by nothing, so a closed IDE read live for 20 minutes and a quiet-but-open session read dead. And those hooks were installer-only, so no already-installed project (this repo included) wrote session_open / session_end at all. - services/session_live.py: one heartbeat file per session under .c3/live, atomic write, 60 s beat / 180 s TTL. Written by the MCP lifespan on a daemon thread and cleared before the slow teardown. - ProjectManager._get_live_sessions: heartbeats first, activity-log inference as fallback, session_end honoured there (matched on session_id OR host_session_id). list_projects gains sessions / session_count; the hub card shows MCP×N. - hook_session_open.ensure_ui: launches the project's UI, gated on C3_NO_UI_AUTOSTART / C3_BENCHMARK_MODE, ui.autostart_on_session, hub registration, and a live registry port (so resume/clear never stack a second server). - Registry entries carry pid / owner / owner_session; sweep_registry reaps only session-owned servers, after ui_reap_minutes (default 30, 0 disables, never on an unreachable path). The hub's one-shot autostart is now a 60 s sweep that also chases live-session projects. - c3_hook_migrations + migrate_hooks_for_project: the migration is a table now, and it ships SessionStart / SessionEnd to existing projects. It also quoted commands with shlex.quote, which on Windows writes a single-quoted backslash path cmd.exe never unquotes — every entry it wrote for a path with a space was dead, hook_c3read since 2.21 included. Both writers now share cli/_hook_utils.hook_command_arg, pinned against the installer. Tests: 41 new across tests/test_session_live.py, tests/test_hub_hook_migration.py, test_project_manager.py and test_hook_session_end.py. Full suite 4011 passed / 7 skipped, ruff clean. docs/session-liveness.md documents the contract and the knobs. Claude-Session: https://claude.ai/code/session_01FLzc3usUhbfdF9M35UUMb5
This was referenced Sep 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Opening an IDE session in a project left you with no UI "in some cases", and the cases were not random: nothing in C3 connected a session starting to a UI server running.
ProjectManager.launch_sessionhad five callers — two hub endpoints, the per-project server, the TUI, and a thread inrun_hubthat fired ONCE, two seconds after hub startup, over projects withautostart_ui=true(true on 0 of the 60 projects registered on the machine this was found on). So when a UI was there, it was a detachedpythonwserver a previous session had left behind — survival, not startup. Reboot,taskkill, or the hub's Stop button, and the next session had nothing.Liveness had the same shape of problem.
_get_live_session_inforead the newestsession_startrow and ended it only on asession_save— written solely when a human clicks end in the hub — or after 20 minutes of tool-call silence:session_endrow added in 2.126.0 was read by nothing;And those 2.126.0 hooks were registered by
c3 install-mcponly, so no already-installed project — this repo included — wrotesession_open/session_endrows at all.What changed
Liveness is a fact now.
services/session_live.pykeeps one heartbeat file per session (.c3/live/<id>.json={session_id, host_session_id, ide, pid, ts}), written throughservices/atomic_json.write_json_atomic, beat every 60 s by the MCP lifespan'sc3-session-heartbeatthread and cleared before the slow teardown steps. The timestamp decides liveness, never a pid probe (pids are reused; no psutil dependency)._get_live_sessionsreads heartbeats first and falls back to the old inference for a session with no heartbeat — and that fallback finally treatssession_endas terminal, matching onsession_idorhost_session_id.list_projectsgainssessions/session_count; every singular field still describes the newest session, so existing consumers are unaffected. The hub card showsMCP×N.The UI follows the session.
cli/hook_session_open.ensure_uilaunches the project's UI when one is not already running. It declines onC3_NO_UI_AUTOSTART/C3_BENCHMARK_MODE, on.c3/config.json → {"ui": {"autostart_on_session": false}}, when the project is not registered with the hub, and when the registry already has a live port for it — the last one is what keepsresumeandclearfrom stacking a second server.source: compactnever reaches it.Ownership and reaping. Registry entries carry
pid,owner(session|user) andowner_session.sweep_registrystops a session-owned server once its project has no heartbeat left and its activity log has been untouched forui_reap_minutes(new~/.c3/hub_config.jsonkey, default 30,0disables; never on an unreachable path), and reports{"dropped", "reaped"}. A server you started withc3 uior Open UI is never touched. The hub's one-shot autostart is now a 60 s sweep that also chasesautostart_uiprojects with a live session, so a hub started after the IDE, or a UI that died mid-session, is no longer a dead end.Hook distribution.
c3_hook_migrations()/migrate_hooks_for_project()turn the hub's migration into a table and shipSessionStart/SessionEndto already-installed projects. Additive, idempotent, never creates a settings file, never drops a user's own hooks.One bug found on the way
That migration quoted paths with
shlex.quote, which on Windows produces a single-quoted backslash path. cmd.exe does not treat single quotes as quoting, so every command it wrote for a path containing a space (every path under1. Projects, say) was dead on arrival — including thehook_c3readentry it has been writing since 2.21. The installer learned this on 2026-07-26 and has used a double-quoted forward-slash path ever since; that logic now lives incli/_hook_utils.hook_command_arg, both writers call it, and a test pins the migration's command string against the installer's byte for byte.Verification
tests/test_session_live.py,tests/test_hub_hook_migration.py,test_project_manager.py,test_hook_session_end.py.ruff check .clean; JSX bundles transpile under real Babel (C3_REQUIRE_JSX_TOOLCHAIN=1 pytest tests/test_ui_jsx_syntax.py).ensure_uiagainst this repo returnsalready-running— it correctly dedupes against the UI already live on 3334..claude/settings.local.json: adds both events, leaves the existing four untouched, and is a no-op on a second run.c3_cicannot reachFULL_CI_PASSlocally — 3 macOS matrix cells can't run on this host.Runtime gate: hooks and the MCP server load their code at process start, so none of this is live until the hub is restarted and a new IDE session opens.
docs/session-liveness.mddocuments the contract and the knobs.https://claude.ai/code/session_01FLzc3usUhbfdF9M35UUMb5