Summary
Two related bugs that compound on a fresh repo and produce a confusing diagnostic:
-
crosslink init does not write tracker_remote into hook-config.json. It relies on a runtime fallback in sync::read_tracker_remote (defaults to "origin" if unset). The fallback is good defensive code, but the absence of an explicit value at init time makes the on-disk config look fine while the in-memory state can drift on crosslink config set calls (we hit a case where setting an unrelated config key flushed a team-preset reapply that visibly altered five other settings — suggesting tracker_remote's effective value was different from what the JSON appeared to say).
-
Push errors when the remote can't be resolved/used are mis-categorized as (offline). The push-failure branches in sync/heartbeats.rs:60, sync/cache.rs:724, and shared_writer/core.rs:331 only check for the literal substrings "Could not resolve host" and "Could not read from remote". Any other failure mode (unknown remote, push refused, refspec error) falls through, and at the call sites the resulting tracing::warn formats the message as "push failed (offline), changes saved locally only". So "wrong remote configured" or "tracker_remote points at nothing valid" gets the same UX as "no network."
Combined effect: a freshly-init'd crosslink workspace whose tracker_remote happens to drift from the actual git remote produces a stream of Warning: push failed (offline), changes saved locally only: <action> messages on every command, with no actionable signal that the actual cause is a config mismatch.
Reproduction
crosslink init in a repo with origin remote.
- Run
crosslink quick "test". Note the (offline) warning.
- Inspect:
cat .crosslink/hook-config.json — tracker_remote may be present (set by team preset) or absent.
- Manual
git push origin crosslink/hub from inside .crosslink/.hub-cache succeeds.
crosslink config set tracker_remote origin (or set any other config key that triggers a write) → next crosslink quick no longer warns.
In our reproduction, the actual tracker_remote value in hook-config.json was already "origin" both before and after the fix command — so the underlying issue was not the literal value but a config-state-flush. That smells like a layered config (team preset vs. file vs. memoized read) where the layers can disagree until forcibly reconciled.
Suggested fixes
Fix 1 (init): At crosslink init time, write tracker_remote explicitly into hook-config.json. Detect the value by:
If exactly one remote exists, use it. If multiple, prefer origin if present; otherwise prompt or fail with a clear message asking the user to pick. This removes any reliance on the runtime fallback for normal flows.
Fix 2 (error categorization): Replace the substring-based offline detection in the three push paths with a structured classifier that distinguishes:
- offline / network unreachable (
Could not resolve host, Could not read from remote, Connection timed out, Network is unreachable)
- remote misconfigured (
'<remote>' does not appear to be a git repository, fatal: '<remote>' does not appear, no such remote)
- non-fast-forward / rejected (
! [rejected], non-fast-forward)
- auth / permission (
Permission denied, 403, Authentication failed)
- other / unknown
The (offline) warning should fire only for the first bucket. The misconfigured bucket should print actionable guidance: "crosslink couldn't push to remote <name>; configure it with 'crosslink config set tracker_remote <existing-remote>' or 'git remote add <name> <url>'".
Fix 3 (consistency): The current text "Warning: push failed (offline), changes saved locally only: <action>" (shared_writer/core.rs:335) is at tracing::warn level so it always shows. Either downgrade to info for true offline (transient; common on laptops) or surface a one-time prompt on the first failure with the actionable diagnosis from fix 2.
File:line references (against current HEAD of /home/doll/crosslink/crosslink/src)
- Fallback default:
sync/mod.rs:43-68 — read_tracker_remote
- Three push call sites that classify "(offline)":
sync/cache.rs:719-728 — commit_and_push_locks
sync/heartbeats.rs:57-64 — push_heartbeat
shared_writer/core.rs:326-339 — generic git_commit_in_cache_with_args push
Severity
Medium. Doesn't block work — the local cache stays consistent and a manual push round-trips state — but it produces noise on every single command and obscures real configuration problems. Combined with crosslink #585 (driver-key signing path), a fresh-clone or post-WSL-migration workflow currently has two cryptic-error-paper-cuts that an experienced user can debug only by reading the source.
Environment
- Surfaced on a proto-blue workspace (Rust atproto SDK) after migrating to a fresh WSL instance with new SSH keys.
- Other repos in the same WSL with a properly-set
tracker_remote worked correctly throughout, confirming the root cause is local-config-shaped, not crosslink-as-a-whole.
🤖 Generated with Claude Code
Summary
Two related bugs that compound on a fresh repo and produce a confusing diagnostic:
crosslink initdoes not writetracker_remoteintohook-config.json. It relies on a runtime fallback insync::read_tracker_remote(defaults to"origin"if unset). The fallback is good defensive code, but the absence of an explicit value at init time makes the on-disk config look fine while the in-memory state can drift oncrosslink config setcalls (we hit a case where setting an unrelated config key flushed a team-preset reapply that visibly altered five other settings — suggestingtracker_remote's effective value was different from what the JSON appeared to say).Push errors when the remote can't be resolved/used are mis-categorized as
(offline). The push-failure branches insync/heartbeats.rs:60,sync/cache.rs:724, andshared_writer/core.rs:331only check for the literal substrings"Could not resolve host"and"Could not read from remote". Any other failure mode (unknown remote, push refused, refspec error) falls through, and at the call sites the resulting tracing::warn formats the message as"push failed (offline), changes saved locally only". So "wrong remote configured" or "tracker_remote points at nothing valid" gets the same UX as "no network."Combined effect: a freshly-init'd crosslink workspace whose tracker_remote happens to drift from the actual git remote produces a stream of
Warning: push failed (offline), changes saved locally only: <action>messages on every command, with no actionable signal that the actual cause is a config mismatch.Reproduction
crosslink initin a repo withoriginremote.crosslink quick "test". Note the(offline)warning.cat .crosslink/hook-config.json—tracker_remotemay be present (set by team preset) or absent.git push origin crosslink/hubfrom inside.crosslink/.hub-cachesucceeds.crosslink config set tracker_remote origin(or set any other config key that triggers a write) → nextcrosslink quickno longer warns.In our reproduction, the actual
tracker_remotevalue in hook-config.json was already"origin"both before and after the fix command — so the underlying issue was not the literal value but a config-state-flush. That smells like a layered config (team preset vs. file vs. memoized read) where the layers can disagree until forcibly reconciled.Suggested fixes
Fix 1 (init): At
crosslink inittime, writetracker_remoteexplicitly intohook-config.json. Detect the value by:If exactly one remote exists, use it. If multiple, prefer
originif present; otherwise prompt or fail with a clear message asking the user to pick. This removes any reliance on the runtime fallback for normal flows.Fix 2 (error categorization): Replace the substring-based offline detection in the three push paths with a structured classifier that distinguishes:
Could not resolve host,Could not read from remote,Connection timed out,Network is unreachable)'<remote>' does not appear to be a git repository,fatal: '<remote>' does not appear, no such remote)! [rejected],non-fast-forward)Permission denied,403,Authentication failed)The
(offline)warning should fire only for the first bucket. The misconfigured bucket should print actionable guidance:"crosslink couldn't push to remote <name>; configure it with 'crosslink config set tracker_remote <existing-remote>' or 'git remote add <name> <url>'".Fix 3 (consistency): The current text
"Warning: push failed (offline), changes saved locally only: <action>"(shared_writer/core.rs:335) is attracing::warnlevel so it always shows. Either downgrade toinfofor true offline (transient; common on laptops) or surface a one-time prompt on the first failure with the actionable diagnosis from fix 2.File:line references (against current HEAD of /home/doll/crosslink/crosslink/src)
sync/mod.rs:43-68—read_tracker_remotesync/cache.rs:719-728—commit_and_push_lockssync/heartbeats.rs:57-64—push_heartbeatshared_writer/core.rs:326-339— genericgit_commit_in_cache_with_argspushSeverity
Medium. Doesn't block work — the local cache stays consistent and a manual push round-trips state — but it produces noise on every single command and obscures real configuration problems. Combined with crosslink #585 (driver-key signing path), a fresh-clone or post-WSL-migration workflow currently has two cryptic-error-paper-cuts that an experienced user can debug only by reading the source.
Environment
tracker_remoteworked correctly throughout, confirming the root cause is local-config-shaped, not crosslink-as-a-whole.🤖 Generated with Claude Code