fix(relay): c2c restart relay-connect no longer raises uncaught Not_found (B212)#8
Open
XertroV wants to merge 115 commits into
Open
fix(relay): c2c restart relay-connect no longer raises uncaught Not_found (B212)#8XertroV wants to merge 115 commits into
XertroV wants to merge 115 commits into
Conversation
… it? grok likes to call it. not sure if it's a real skill or how it's injected into the client.
… monitor a given session (the jsonl file) and filter out all the noise (tool calls, thinking, etc) and forward user input and agent plaintext chat output to anoter c2c address. The idea is to allow monitoring or observation, esp to remote sessions like on a colleagues PC.
…& c2c blah' at the same time and get different aliases. This should not be possible. We should consider the starting location's git root + session id; anything else we need to be careful with.
…ll monitor a given session (the jsonl file) and filter out all the noise (tool calls, thinking, etc) and forward user input and agent plaintext chat output to anoter c2c address. The idea is to allow monitoring or observation, esp to remote sessions like on a colleagues PC.
…ed it? grok likes to call it. not sure if it's a real skill or how it's injected into the client.
…g as designed (no code change)
…, not a c2c-session defect)
…cross repos B188 made auto-register surfaces reuse a session_id's sticky alias from other per-repo brokers, but the scan->register sequence was not atomic across brokers: two concurrent registrations of one session under two different git roots both scanned empty and minted distinct aliases. Add a machine-global advisory lock keyed by session id (~/.c2c/locks/session-reg-<sha256(sid)[0:16]>.lock, C2C_STATE_HOME chain, lockf + O_CLOEXEC, best-effort) held across [cross-broker scan -> alias choice -> Broker.register] on every registration surface: - c2c_mcp_helpers_post_broker: acquire/release/with_session_registration_lock + locked_sticky_auto_register (resolve -> key-migrate -> register under the lock); auto_register_impl body now runs under the lock. - hooks (codex/claude/grok/agy) + c2c send auto-register: switch to locked_sticky_auto_register (also DRYs the duplicated key migration). - c2c init / c2c register: imperative acquire before the sticky scan, release after register (error-path exits release via the kernel). Semantics (documented): reuse, not refuse — a session working in several repos registers in each repo's broker under the same alias; live-occupied alias in the target broker still falls back to minting (hijack guard); explicit --alias, B135 sticky refusal, B140 rename, worktrees and --cross-repo behavior unchanged. Tests: ocaml/test/test_b191_session_reg_lock.ml — lock path determinism, fork-based mutual exclusion, and the concurrent cross-repo race regression via both locked_sticky_auto_register and auto_register_impl. Docs: docs/architecture.md, docs/commands.md, CLAUDE.md; design note at .collab/findings/2026-07-15T03-24-47Z-B191-alias-git-root-session-id.md
…c2c peer New Tier-1 subcommand: c2c forward-agent-log --file <session.jsonl> TO follows a Claude Code session transcript (poll-based tail, like deliver watch) and forwards ONLY the human-visible conversation to a c2c address via the normal broker send path (C2c_watch_data.send_dm; alias@host works): - user input forwarded as '[user] ...', assistant plaintext as '[agent] ...' - noise dropped: tool_use/tool_result, thinking, system/meta/summary/mode/ attachment lines, isMeta + isSidechain lines, system-reminder and local-command-output injections, c2c-envelope-delivered mail, interrupt markers; noise text blocks are filtered per-block so mixed user turns keep the typed text - only newline-complete lines are consumed (a live mid-write transcript never yields garbage); malformed lines are skipped; truncation/rotation resets the tail - flood control: starts at EOF by default (--from-start replays history); --max-bytes (default 2000) truncates UTF-8-safely with a note; --once drains the current file and exits; --dry-run prints instead of sending - format registry (--format claude) so other clients' transcript formats can be added later Core logic lives in C2c_forward_agent_log (no CLI-helper deps) with the Cmdliner wiring in C2c_forward_agent_log_cmd. Tier1 (same safety class as monitor + send: read-only local file tail + ordinary bus sends; B098-inert — forwarded content is DATA, no approval/RPC semantics). Tests (22, alcotest): pure classify/tail/format units, committed fixture transcript replay (ocaml/cli/test_fixtures/forward_agent_log_claude.jsonl, incl. trailing partial line), real temp-broker delivery via read_inbox, and binary --once runs gated by C2C_SEND_MESSAGE_FIXTURE=1. Also live-fire tested against a growing temp transcript + temp broker. Docs: docs/commands.md Tier-1 table row + changelog entry (embedded blob regenerated via just codegen-changelog). just check passes.
…, e2e evidence Covers: scan->register race left open by B188; global per-session registration lock design; reuse-not-refuse multi-repo semantics; review notes (EINTR degrade, exit_floored lock-hold, MCP register-tool non-minting gap); in-the-wild sequential + concurrent evidence with the built binary.
…lias across repos
… c2c blah' at the same time and get different aliases. This should not be possible. We should consider the starting location's git root + session id; anything else we need to be careful with.
…d it? grok likes to call it. not sure if it's a real skill or how it's injected into the client.
…l monitor a given session (the jsonl file) and filter out all the noise (tool calls, thinking, etc) and forward user input and agent plaintext chat output to anoter c2c address. The idea is to allow monitoring or observation, esp to remote sessions like on a colleagues PC.
… --root . + opam exec
…ncode, kimi, grok, agy), not just claude — per-format classifiers + auto-detect + opencode dir source
…pencode, kimi, grok, agy), not just claude — per-format classifiers + auto-detect + opencode dir source
…bSocket subscribe
… opencode) - classifiers for codex event_msg rollouts, kimi context.jsonl roles, grok chat_history.jsonl (<user_query> extraction, synthetic turns), agy/gemini chats jsonl (message + $set journal lines) - opencode has no single transcript: new polled directory source over storage/message/<sessionID> joined with storage/part/<msgID> text parts; user messages forward on appearance, assistant once time.completed - --format defaults to auto: path heuristics over the standard session stores, falling back to a first-lines content sniff - shared user-noise prefix filter extended (<user_instructions>, <environment_context>, <permissions instructions>, <session_context>) - docs: commands.md row, man page, changelog (B193 entry extended) - tests: per-client classify, detect-format, opencode source semantics, binary e2e for auto-sniff + opencode dir + dir/format mismatch - validated against live sessions of all six clients (dry-run)
…encode, kimi, grok, agy), not just claude — per-format classifiers + auto-detect + opencode dir source
… size of incoming msg (default), per-sender max-size, default per-sender rate limit, individual per-sender rate limit, per-machine incoming rate limit, etc. All of this I think can be managed locally so that we don't need to send this stuff to the relayer.
…/machine/agent have? Particularly for untrusted incoming sources, but maybe also safeguarsd for local sources
…hout proc env dumping
…paction-aware replay - follow mode warns (stderr, non-fatal) when it detects an agent session (is_agent_session) running it in the foreground: streaming blocks the turn; re-run backgrounded or use --once - --since/--until TIME bound the replay to a range (ISO-8601 UTC or epoch seconds; imply --from-start). Supported where events carry timestamps: claude/codex/agy line 'timestamp', opencode time.created; kimi and grok transcripts carry none and get a clear error - replays (--from-start/--once) now start at the transcript's most recent compaction boundary by default (claude compact_boundary, codex compacted/context_compacted); --full-history opts out. Live claude test: 4 messages forwarded vs 3903 with --full-history - fix: claude isCompactSummary user lines (the post-compaction context blob) no longer leak to the observer as [user] messages - new: parse_time_spec (UTC civil-date math, no new deps), replay_start_offset, line_time/compaction format registries - tests: 44 total (time parsing, range filters, markers, offset replay, opencode range, binary e2e for warning/slice/trim); docs + changelog
…hould be autorestarted when c2c updates, and connecting to the relay should only be required once (each agent still needs to register their alias if they want to recieve messages via <alias>@<relay-hostname>). Undecided whether registration should be required for delivery via 'alias@machineid'
…gistrations, triggering HTTP 429 rate limit
…should be autorestarted when c2c updates, and connecting to the relay should only be required once (each agent still needs to register their alias if they want to recieve messages via <alias>@<relay-hostname>). Undecided whether registration should be required for delivery via 'alias@machineid'
… operator when unsure
…egistrations, triggering HTTP 429 rate limit
…y binding' on Grok (and possibly CLI-first clients)
… and gets signature_invalid
Search previously matched only the 120-char eventLabel preview, so tokens deep in a message body never hit. Add eventSearch helpers, clear/Esc, and match counts; unit tests cover deep matches. User-facing "swarm" chrome (window title, wizard, header) → agent/ c2c wording. Default room id swarm-lounge unchanged for compatibility.
Default alias was "human", so skip never showed observer mode. Use empty default, banner + "join as peer" to re-open WelcomeWizard, and stop double-joining the default room on complete.
Optimistic feed events used live form state after send, so retries could attach the wrong target/body. Use attempt fields; classify CLI stdout as delivered|queued|unknown; toast and longer confirm labels (sent ✓ / queued ⏳). Prefer `rooms send` for room messages.
Grok CLI-first monitor looped on signature_invalid because it peeked the cli-<alias>/cli-<alias> key while the machine relay connector owned the alias's single live lease under (host_hash, real_session_id). The relay keys leases by (node_id, session_id) but enforces one row per alias (ON CONFLICT(alias) DO UPDATE), so the connector's register/heartbeat moves the lease off the CLI convention; peeking cli-<alias> then fails the relay owner check. The connector now persists alias -> session_id in connector-state.json (sessions map). New pure connector_peek_key returns the connector's recorded (node_id, session_id) for a managed alias, falling back to the locally-resolved session id only for older state files. The monitor uses it, so it peeks the exact binding register/connect established regardless of local session-id resolution, and connector heartbeats keep it fresh. Regression tests: ocaml/test/test_c2c_relay_connector.ml (B209 group). Client-only; no relay-server change, no deploy needed.
Skip when focus is already in an input/textarea; Esc clears and blurs.
…process stays alive
…ead of recovering connector
… after connector-key fix
…ound (B212) `c2c restart relay-connect` fell through to the harness-client restart path (cmd_restart -> load_config_opt), whose `gs` helpers raise Not_found on the machine connector's minimal supervisor config.json (client/scope/supervised/ relay_url/interval — no name/session_id/alias/auto_join_rooms). The exception was uncaught, crashing the command (exit 125) and leaving the connector unchanged, so whoami's remediation pointed at a broken command. Fixes: - c2c_managed_cmd.restart_cmd detects the relay-connect connector by its config `client` field and restarts it via the machine-supervisor lifecycle: stop the current supervisor (SIGTERM outer.pid, wait, SIGKILL on timeout) so the machine singleton lock is released, then start a fresh supervisor reusing the persisted relay_url + interval. When no relay URL is known it prints a clear, actionable error (exit 1) instead of crashing. - C2c_relay_managed.restart / restart_params_of_config implement the connector restart + parameter resolution (config relay_url, else $C2C_RELAY_URL, both trimmed; empty treated as unset). - load_config_opt is now total: a config.json lacking required harness-client fields returns None (matching its .mli contract) rather than escaping Not_found — closing the whole uncaught-exception class on the restart path. whoami/doctor remediation (`c2c restart relay-connect ...`) now names a working managed-lifecycle command. Tests: - test_c2c_relay_managed: restart_params_of_config resolves url from config, falls back to $C2C_RELAY_URL, and errors clearly when neither is set. - test_c2c_start: load_config_opt returns None (no Not_found) on a minimal relay-connect connector config. Fixes #6 Addresses backlog B212.
… (B212) Follow-up hardening on the B212 restart fix. `C2c_relay_managed`'s supervisor-lifecycle paths resolved the instances dir from $HOME and ignored $C2C_INSTANCES_DIR, while `restart_cmd` resolved config.json via `C2c_start.instance_dir` (which honors $C2C_INSTANCES_DIR). So `c2c restart relay-connect` with $C2C_INSTANCES_DIR set read config from the override dir but SIGKILLed the outer.pid under $HOME — i.e. it would kill the real machine supervisor. This PR shipped that hazard; close it. - C2c_relay_managed.instances_dir now honors $C2C_INSTANCES_DIR with the same semantics as C2c_start.instances_dir (non-blank env wins, else the historical $HOME/.local/share/c2c/instances default). machine_state_dir (home of the singleton lock) is derived as its parent, so the lock tracks the same base: an override dir gets an isolated lock; unset stays $HOME/.local/share/c2c. - Extracted connector_pid_path + stop_supervisor so restart's outer.pid read, the machine lock, and the fresh supervisor all resolve through one env-honoring instances_dir. Pure consistency: env-UNSET behavior is byte-for-byte unchanged (existing test_machine_lock_is_name_independent still passes). Singleton-lock model itself is unchanged (B210 is separate). Tests (test_c2c_relay_managed): - instances_dir + machine_lock_resource honor C2C_INSTANCES_DIR (override → temp base; connector pid path is NOT under the real $HOME default). - unset/blank falls back to the $HOME default (production unchanged). - restart's stop step signals ONLY the supervisor under $C2C_INSTANCES_DIR and leaves an outer.pid under the $HOME path untouched. Verified end-to-end: `c2c restart relay-connect` with C2C_INSTANCES_DIR + HOME sandboxed signals the override supervisor and leaves the HOME-path supervisor alive. Builds/tests -j 2; just check PASS. Addresses backlog B212 (review follow-up).
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.
Summary
c2c restart relay-connectraised an uncaughtNot_found(exit 125) and left the connector unchanged, instead of restarting it or printing a clear error. Becausewhoami/doctor advertisec2c restart relay-connectas the recovery command for a wedged/registered_unreachableconnector, the advertised recovery path was broken.Fixes #6. Addresses backlog B212.
Root cause
c2c restart <name>resolves the instance viaC2c_start.load_config_opt, whosegs/gf/glhelpersraise Not_foundon any missing key. The machine-wide relay connector persists only a minimal supervisor config (client/scope/supervised/relay_url/interval) — it has none of the harness-client fields (name,session_id,resume_session_id,alias,auto_join_rooms). Soload_config_optraisedNot_found, uncaught, fromcmd_restart(c2c_start.ml:5836) viarestart_cmd(c2c_managed_cmd.ml).Reproduced on current
master(binary built from HEAD):Fix
c2c_managed_cmd.restart_cmddetects the relay-connect connector by its configclientfield and restarts it through its own machine-supervisor lifecycle instead of the harness path: stop the current supervisor (SIGTERMouter.pid, wait up to timeout, SIGKILL) so the machine singleton lock is released, then start a fresh supervisor reusing the persistedrelay_url+interval. If no relay URL is known, it prints a clear, actionable error (exit 1) rather than crashing.C2c_relay_managed.restart/restart_params_of_configimplement the restart and parameter resolution (configrelay_url, else$C2C_RELAY_URL; both trimmed, empty treated as unset).load_config_optis now total: aconfig.jsonlacking required harness fields returnsNone(matching its.mlicontract) rather than escapingNot_found. This closes the whole uncaught-exception class on the restart path.With the primary command working, the
whoami/doctor remediation (c2c restart relay-connect ...) now names a real, working managed-lifecycle command.Tests
test_c2c_relay_managed:restart_params_of_configresolves the URL from config, falls back to$C2C_RELAY_URL, and errors clearly when neither is set.test_c2c_start:load_config_optreturnsNone(noNot_found) on a minimal relay-connect connector config.Verification
just check— PASS (full-treedune buildincl. server + tools, catalog + doc-command gates).test_c2c_relay_managed— 9/9 OK.test_c2c_start— 198/198 OK.HOME:Not_found, nothing spawned.Builds/tests limited to
-j 2per repo system constraint.Notes / follow-ups
whoami/doctor remediation fallback (pkill -f 'c2c relay connect'; c2c relay connect &) spawns an unmanaged connector, which can contribute to B210-style multi-connect churn. Now that the primary managedc2c restart relay-connectworks, that fallback should rarely trigger; tightening it is a possible follow-up but is out of scope here.c2c_relay_managed.ml, connector restart); no changes to the connector sync/watchdog logic itself.