You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Attach 2code to the user's live Herdr session for parent plan #466. Production today starts a private session named 2code ($XDG_CONFIG_HOME/herdr/sessions/2code/herdr.sock, short-socket /tmp/2code-herdr-*.sock fallback) and resolve_namespacerefuses the user default socket ($XDG_CONFIG_HOME/herdr/herdr.sock). The user's own herdr is the default session. Two servers, two disjoint workspace/pane sets. Replace that with CLI-equivalent resolution of the user's session, start the default socket when nothing is running, and never spawn a private 2code server. Same clicks and labels. Seeing the user's real workspaces (join-keyed to the open project) is the point of this phase, not a redesign.
Herdr is the profile authority. sqlite profiles is not source of truth (the table is DROPped). projects / project_groups / checkout_notes stay in 2code sqlite. Default-runtime switch and deleting Local/PTY/profiles rows already shipped in #436 — do not re-litigate or re-add them. Never let Local and Herdr own the same worktree (Local is gone; do not bring it back). Do not change interaction. Reuse the existing socket client, snapshot/event sync, frame bridge, and worktree/pane RPCs — this task changes which socket 2code talks to, not the protocol layer.
This is #466 Task 1, not #394 Task 1 (#395), not #436 Task 1 (#437), and not #463 Task 1 (#464). Do not implement or close those tickets here. Do not start Task 2 (version policy / prefer user binary / herdr update) or Task 3 (bidirectional e2e proof + sharing-contract docs rewrite).
Dependencies: Parent plan #466. Prerequisites: none on this plan. Stack on task-3-prove-herdr-only (#467 / parent #463). Open task tickets from #394 / #436 / #458 / #463, leftover gaps (#396, #397, #398, #399, #401), and line-count trackers are out of scope.
Work to complete
Resolve the user's live session the way the herdr CLI does
Verified order in docs/herdr-integration.md: --session / HERDR_SESSION named path wins; else HERDR_SOCKET_PATH; else $XDG_CONFIG_HOME/herdr/herdr.sock. Named sessions live at $XDG_CONFIG_HOME/herdr/sessions/<name>/herdr.sock.
Replace resolve_namespace so production attaches to that shared socket. Honor process-inherited HERDR_SESSION / HERDR_SOCKET_PATH (user launched 2code from that environment). If herdr session list exists and reports a running named session while the default socket is absent, attach to that live session rather than starting a second server.
Delete SESSION_NAME = "2code", native_session_socket (sessions/2code/herdr.sock), short_socket_path (/tmp/2code-herdr-*.sock), and the refusing the user default Herdr session guard. If the resolved default path exceeds sun_path, fail closed naming the path — do not invent a private short socket.
apply_namespace_env must stop forcing HERDR_SESSION=2code. Point HERDR_SOCKET_PATH at the resolved shared socket only. CLI attach in terminal.rs must stop passing --session 2code and stop refusing namespace.session != "2code".
endpoint_not_allowed must stop refusing $XDG_CONFIG_HOME/herdr/herdr.sock. Keep refusing herdr-client.sock / *-client.sock (binary frame helper, not the JSON API).
classify_status must stop treating a running default session as "server is not in the dedicated 2code session". Compatible = the resolved shared session is running. Keep today's exact v0.9.0 / protocol-22 pin (Task 2 relaxes that). An incompatible reachable server still fails closed and must not spawn a private 2code instance.
Start the shared default server when absent; never stop it on exit
If no server is running, start one on the user's default socket (herdr/herdr.sock under the resolved XDG), so the user's own herdr attaches to the same instance. Still use the pinned sidecar binary for that start (Task 2 prefers the user's installed herdr).
GUI exit / HerdrClientGuard drop / release_herdr_client_helpers still must not herdr server stop or kill the shared server. Existing behaviour, keep it.
Tests that isolate via a fixture XDG_CONFIG_HOME stay isolated: their "default socket" is $fixture_xdg/herdr/herdr.sock, not the developer's ~/.config/herdr/herdr.sock. Do not retarget the contract probe onto the host's real session. Probe isolation via HERDR_SOCKET_PATH / short /tmp/2c*.sock is test isolation, not a production private 2code namespace — leave that probe machinery; do not delete it in this task.
Prove a 2code-created session is visible on the default socket
Invert tests that currently lock the private namespace:
process.rs: namespace_is_dedicated_2code_never_default, long_xdg_falls_back_to_short_socket_not_default, probe_never_uses_the_default_session, assert_namespace_env (HERDR_SESSION=2code and "must not target the default session"), live live_probe_start_reuse_and_gui_drop_leave_server (today asserts the default socket stays not running).
transport.rs: default_session_and_client_socket_are_refused (default JSON socket must be allowed; client socket still refused), live_ping_and_snapshot_on_2code_namespace.
terminal.rs: control_spawn_uses_2code_namespace_without_takeover and HERDR_SESSION=2code env asserts. Keep refusing silent --takeover.
Add (or retarget a live test to) the parent-plan proof: through 2code's runtime path, create a workspace/pane on an isolated XDG, then show that workspace in herdr api snapshot / session.snapshoton that XDG's default socket with no HERDR_SESSION=2code and no sessions/2code/ path. Lease drop leaves that default socket live. Quote the command and a sanitized snippet in the implementation reply.
Fake-herdr unit tests: absent still starts one detached server on the default socket; compatible default is reused without a second start; incompatible does not start or stop; drop does not server stop.
Update live current-state docs; do not swallow Task 2 or Task 3
Rewrite production current-state sentences that still say dedicated 2code namespace / never the user default session: docs/architecture.md infra table, docs/configuration.md, docs/herdr-integration.md opening current-state paragraph, src-tauri/src/bridge.rs / runtime module comments. AGENTS.md / CLAUDE.md copies that already share that sentence stay in sync.
Keep contract-probe isolation notes that the dump uses a fixture socket so it does not hijack the developer's real default session.
Do not write Task 3's bidirectional command log (herdr workspace list reverse direction) or replace the whole sharing-contract section. Do not change sidecar pin, prefer-user-binary, protocol >= 22, or the herdr update error copy (Task 2).
If line count grows past the estimate, open a tracking issue; do not fold Task 2 or Task 3 into this branch.
Estimated changes: 400–700 lines total, additions plus deletions, including tests and docs; excluding generated files, lockfile churn and binary artifacts. Material scope growth (user-binary preference, protocol >= 22 / herdr update, bidirectional e2e docs) should become a separate issue.
Acceptance criteria
No production path creates or references a private Herdr session named 2code. SESSION_NAME = "2code", sessions/2code/herdr.sock, /tmp/2code-herdr-*.sock, HERDR_NAMESPACE = "2code", HERDR_SESSION=2code, --session 2code, and refusing the user default Herdr session are gone from live code (tests that previously locked those must invert).
resolve_namespace / ensure_herdr_listener attach to the user's CLI-equivalent socket (inherited HERDR_SOCKET_PATH / HERDR_SESSION, else default $XDG_CONFIG_HOME/herdr/herdr.sock; live named session from herdr session list if default is absent). If nothing is running, start on the default socket. Never start a second, private server.
A workspace/pane created through 2code's runtime on an isolated XDG appears in herdr api snapshot / session.snapshot on that XDG's default socket. The implementation reply quotes the command and a sanitized snippet. GUI/lease drop leaves that server running (server stop still absent from exit).
endpoint_not_allowed allows the default JSON socket and still refuses herdr-client.sock. Terminal attach still refuses silent --takeover.
Incompatible reachable server: fail closed, no private 2code spawn. Keep today's exact v0.9.0 / protocol-22 classification (do not add herdr update copy or prefer-user-binary; that is Task 2). Absent vs incompatible stay distinct.
Live current-state docs no longer say GUI startup uses a dedicated 2code namespace and never the user default session. Contract-probe notes may still isolate a fixture socket from the host's real default. v0.9.0 pin unchanged.
No behavior change beyond which socket is shared. sqlite profiles is not source of truth. projects still round-trip in sqlite. No Local adapter. No second events.subscribe. lib.rs still does not name HerdrRuntimeSync. Join key still filters profiles by project folder.
Named UX exceptions: none new. Existing Herdr-backed exceptions stay (route ids workspace_id; tab ids pane_id; splits flattened as extra tabs; non-git New Profile is folder workspace.create; Herdr-down New Tab errors). Showing join-keyed user workspaces in the sidebar is the phase goal, not a click/label redesign.
Tests: cargo test --workspace from src-tauri (or cargo test --workspace --exclude code where GTK is unavailable), including process/terminal/transport live tests when the pinned binary is present. Frontend lint/typecheck/tests only if frontend scope is introduced (none expected). bun run typegen only if IPC signatures change (none expected); do not hand-edit src/generated/.
No push. No pull request.
Out of scope
Parent-plan Task 2 (version policy, prefer user herdr over sidecar, protocol >= 22, actionable herdr update message) and Task 3 (bidirectional e2e proof + sharing-contract docs rewrite with command output).
Retargeting the contract probe onto the developer's real ~/.config/herdr session.
Reintroducing a Local adapter, Settings runtime toggle, portable-pty spawn, or sqlite profiles / session-table authority.
Changing the Herdr-only runtime decision, the v0.9.0 pin, GUI exit / server stop, flattened-split mapping, IPC command names, or Git/file-tree/notes/tab UI.
Never start a private session. 2code as a session name, sessions/<name>/herdr.sock used as 2code's production socket, and the default-socket refusal are deleted. Nothing in 2code may spawn a server on a socket the user's own herdr cannot see.
Never stop the user's server. GUI exit, disconnect, crash and update must not issue herdr server stop or kill the shared server process.
Fail with an actionable message, never with silence. Absent vs incompatible stay distinct. The herdr update copy and protocol >= 22 policy are Task 2; this task must not paper over an old server by spawning a private instance.
Do not change interaction. Same clicks and labels. If a label genuinely cannot survive, name the exception in this issue (none new).
Reuse the code that already works — socket client, snapshot/event sync, frame bridge, worktree and pane RPCs.
Do not treat sqlite profiles as source of truth. projects stay in 2code sqlite.
Never let Local and Herdr own the same worktree (Local is already removed; do not bring it back).
One task; stack on task-3-prove-herdr-only. Do not push. Do not open a PR.
Why this is next
#466 has three tasks. None are done and none have sub-issues. Task 1 is the socket/session switch; Tasks 2–3 depend on 2code talking to the user's default session instead of sessions/2code/. The leftover open gaps (#396/#397/#398/#399/#401) are Linux-unverifiable or upstream-capability leftovers, not in-flight replacements for this work. #395 is #394 Task 1. #437 is #436 Task 1. #464 is #463 Task 1.
Summary
Attach 2code to the user's live Herdr session for parent plan #466. Production today starts a private session named
2code($XDG_CONFIG_HOME/herdr/sessions/2code/herdr.sock, short-socket/tmp/2code-herdr-*.sockfallback) andresolve_namespacerefuses the user default socket ($XDG_CONFIG_HOME/herdr/herdr.sock). The user's ownherdris thedefaultsession. Two servers, two disjoint workspace/pane sets. Replace that with CLI-equivalent resolution of the user's session, start the default socket when nothing is running, and never spawn a private2codeserver. Same clicks and labels. Seeing the user's real workspaces (join-keyed to the open project) is the point of this phase, not a redesign.Herdr is the profile authority. sqlite
profilesis not source of truth (the table is DROPped).projects/project_groups/checkout_notesstay in 2code sqlite. Default-runtime switch and deleting Local/PTY/profilesrows already shipped in #436 — do not re-litigate or re-add them. Never let Local and Herdr own the same worktree (Local is gone; do not bring it back). Do not change interaction. Reuse the existing socket client, snapshot/event sync, frame bridge, and worktree/pane RPCs — this task changes which socket 2code talks to, not the protocol layer.This is #466 Task 1, not #394 Task 1 (#395), not #436 Task 1 (#437), and not #463 Task 1 (#464). Do not implement or close those tickets here. Do not start Task 2 (version policy / prefer user binary /
herdr update) or Task 3 (bidirectional e2e proof + sharing-contract docs rewrite).Dependencies: Parent plan #466. Prerequisites: none on this plan. Stack on
task-3-prove-herdr-only(#467 / parent #463). Open task tickets from #394 / #436 / #458 / #463, leftover gaps (#396, #397, #398, #399, #401), and line-count trackers are out of scope.Work to complete
Resolve the user's live session the way the
herdrCLI doesdocs/herdr-integration.md:--session/HERDR_SESSIONnamed path wins; elseHERDR_SOCKET_PATH; else$XDG_CONFIG_HOME/herdr/herdr.sock. Named sessions live at$XDG_CONFIG_HOME/herdr/sessions/<name>/herdr.sock.resolve_namespaceso production attaches to that shared socket. Honor process-inheritedHERDR_SESSION/HERDR_SOCKET_PATH(user launched 2code from that environment). Ifherdr session listexists and reports a running named session while the default socket is absent, attach to that live session rather than starting a second server.SESSION_NAME = "2code",native_session_socket(sessions/2code/herdr.sock),short_socket_path(/tmp/2code-herdr-*.sock), and therefusing the user default Herdr sessionguard. If the resolved default path exceedssun_path, fail closed naming the path — do not invent a private short socket.HERDR_NAMESPACE = "2code"(unused outside its own unit test).apply_namespace_envmust stop forcingHERDR_SESSION=2code. PointHERDR_SOCKET_PATHat the resolved shared socket only. CLI attach interminal.rsmust stop passing--session 2codeand stop refusingnamespace.session != "2code".endpoint_not_allowedmust stop refusing$XDG_CONFIG_HOME/herdr/herdr.sock. Keep refusingherdr-client.sock/*-client.sock(binary frame helper, not the JSON API).classify_statusmust stop treating a running default session as"server is not in the dedicated 2code session". Compatible = the resolved shared session is running. Keep today's exact v0.9.0 / protocol-22 pin (Task 2 relaxes that). An incompatible reachable server still fails closed and must not spawn a private2codeinstance.Start the shared default server when absent; never stop it on exit
herdr/herdr.sockunder the resolved XDG), so the user's ownherdrattaches to the same instance. Still use the pinned sidecar binary for that start (Task 2 prefers the user's installedherdr).HerdrClientGuarddrop /release_herdr_client_helpersstill must notherdr server stopor kill the shared server. Existing behaviour, keep it.XDG_CONFIG_HOMEstay isolated: their "default socket" is$fixture_xdg/herdr/herdr.sock, not the developer's~/.config/herdr/herdr.sock. Do not retarget the contract probe onto the host's real session. Probe isolation viaHERDR_SOCKET_PATH/ short/tmp/2c*.sockis test isolation, not a production private2codenamespace — leave that probe machinery; do not delete it in this task.Prove a 2code-created session is visible on the default socket
process.rs:namespace_is_dedicated_2code_never_default,long_xdg_falls_back_to_short_socket_not_default,probe_never_uses_the_default_session,assert_namespace_env(HERDR_SESSION=2codeand "must not target the default session"), livelive_probe_start_reuse_and_gui_drop_leave_server(today asserts the default socket stays not running).transport.rs:default_session_and_client_socket_are_refused(default JSON socket must be allowed; client socket still refused),live_ping_and_snapshot_on_2code_namespace.terminal.rs:control_spawn_uses_2code_namespace_without_takeoverandHERDR_SESSION=2codeenv asserts. Keep refusing silent--takeover.runtime.rsherdr_errors_stay_distinct(SESSION_NAME == "2code"),model/runtime.rsherdr_namespace_is_dedicated_2code.herdr api snapshot/session.snapshoton that XDG's default socket with noHERDR_SESSION=2codeand nosessions/2code/path. Lease drop leaves that default socket live. Quote the command and a sanitized snippet in the implementation reply.server stop.Update live current-state docs; do not swallow Task 2 or Task 3
2codenamespace / never the user default session:docs/architecture.mdinfra table,docs/configuration.md,docs/herdr-integration.mdopening current-state paragraph,src-tauri/src/bridge.rs/ runtime module comments. AGENTS.md / CLAUDE.md copies that already share that sentence stay in sync.herdr workspace listreverse direction) or replace the whole sharing-contract section. Do not change sidecar pin, prefer-user-binary, protocol>= 22, or theherdr updateerror copy (Task 2).Likely files and directories
src-tauri/crates/infra/src/herdr/process.rs—resolve_namespace,classify_status,apply_namespace_env, start/probe, colocated tests.src-tauri/crates/infra/src/herdr/terminal.rs— attach argv/env; stop--session 2code.src-tauri/crates/infra/src/herdr/transport.rs—endpoint_not_allowed; live snapshot test.src-tauri/crates/service/src/runtime.rs(ensure_herdr_listener/connect_gui_herdrcomments andSESSION_NAMEasserts),runtime/herdr.rsCLI-namespace comments/tests,src-tauri/src/bridge.rs.src-tauri/crates/model/src/runtime.rsHERDR_NAMESPACE.docs/architecture.md,docs/configuration.md,docs/herdr-integration.md(current-state production sentences only). AGENTS.md / CLAUDE.md twins only if they still advertise a private2codesession.server stop, flattened-split mapping, IPC names, Git/file-tree/notes/tab UI,lib.rsnamingHerdrRuntimeSync, sqlite catalog schema,src-tauri/migrations/, contract-probe fixture sockets, leftover gaps Herdr v0.9.0 has no Windows live terminal attach (Task 10 gap) #396/Herdr v0.9.0 has no create-time startup command (Task 17 gap) #397/Live Herdr agent detection not verified on v0.9.0 (Task 13 gap) #398/Windows named-pipe JSON transport not verified for Herdr v0.9.0 (Task 5 gap) #399/macOS Unix sockets and terminal session attach not verified for Herdr v0.9.0 (Task 5/10 gap) #401.Estimated changes: 400–700 lines total, additions plus deletions, including tests and docs; excluding generated files, lockfile churn and binary artifacts. Material scope growth (user-binary preference, protocol
>= 22/herdr update, bidirectional e2e docs) should become a separate issue.Acceptance criteria
2code.SESSION_NAME = "2code",sessions/2code/herdr.sock,/tmp/2code-herdr-*.sock,HERDR_NAMESPACE = "2code",HERDR_SESSION=2code,--session 2code, andrefusing the user default Herdr sessionare gone from live code (tests that previously locked those must invert).resolve_namespace/ensure_herdr_listenerattach to the user's CLI-equivalent socket (inheritedHERDR_SOCKET_PATH/HERDR_SESSION, else default$XDG_CONFIG_HOME/herdr/herdr.sock; live named session fromherdr session listif default is absent). If nothing is running, start on the default socket. Never start a second, private server.herdr api snapshot/session.snapshoton that XDG's default socket. The implementation reply quotes the command and a sanitized snippet. GUI/lease drop leaves that server running (server stopstill absent from exit).endpoint_not_allowedallows the default JSON socket and still refusesherdr-client.sock. Terminal attach still refuses silent--takeover.2codespawn. Keep today's exact v0.9.0 / protocol-22 classification (do not addherdr updatecopy or prefer-user-binary; that is Task 2). Absent vs incompatible stay distinct.2codenamespace and never the user default session. Contract-probe notes may still isolate a fixture socket from the host's real default. v0.9.0 pin unchanged.profilesis not source of truth.projectsstill round-trip in sqlite. No Local adapter. No secondevents.subscribe.lib.rsstill does not nameHerdrRuntimeSync. Join key still filters profiles by project folder.workspace_id; tab idspane_id; splits flattened as extra tabs; non-git New Profile is folderworkspace.create; Herdr-down New Tab errors). Showing join-keyed user workspaces in the sidebar is the phase goal, not a click/label redesign.cargo test --workspacefromsrc-tauri(orcargo test --workspace --exclude codewhere GTK is unavailable), including process/terminal/transport live tests when the pinned binary is present. Frontend lint/typecheck/tests only if frontend scope is introduced (none expected).bun run typegenonly if IPC signatures change (none expected); do not hand-editsrc/generated/.Out of scope
herdrover sidecar, protocol>= 22, actionableherdr updatemessage) and Task 3 (bidirectional e2e proof + sharing-contract docs rewrite with command output).~/.config/herdrsession.profiles/ session-table authority.server stop, flattened-split mapping, IPC command names, or Git/file-tree/notes/tab UI.Standing constraints
2codeas a session name,sessions/<name>/herdr.sockused as 2code's production socket, and the default-socket refusal are deleted. Nothing in 2code may spawn a server on a socket the user's ownherdrcannot see.herdr server stopor kill the shared server process.herdr updatecopy and protocol>= 22policy are Task 2; this task must not paper over an old server by spawning a private instance.profilesas source of truth.projectsstay in 2code sqlite.profilesrows already shipped in Plan: 2code as a Herdr client (profiles derived from Herdr) #436; this task does not re-litigate those decisions and does not change them.task-3-prove-herdr-only. Do not push. Do not open a PR.Why this is next
#466 has three tasks. None are done and none have sub-issues. Task 1 is the socket/session switch; Tasks 2–3 depend on 2code talking to the user's default session instead of
sessions/2code/. The leftover open gaps (#396/#397/#398/#399/#401) are Linux-unverifiable or upstream-capability leftovers, not in-flight replacements for this work. #395 is #394 Task 1. #437 is #436 Task 1. #464 is #463 Task 1.