Skip to content

Live Bidirectional Auth Sync

Status: Open — design proposal

jackin’s sync auth mode (today) reads the host login once per container launch, copies the token into the role-state directory, and bind-mounts it. That covers the first-launch convenience case but not three operationally important scenarios:

  1. Host token rotates while a container is already running. gh auth login / claude /login / codex login on the host updates the host’s token store, but every running jackin container keeps using the snapshot it got at launch time. The operator has to jackin eject <role> && jackin load <role> for each container — which means tearing down the agent’s session.
  2. In-container login does not flow back to the host. When an agent inside the container runs gh auth refresh, claude /login, or codex login, the new token persists in that container’s writable layer (and survives via the role-state bind-mount) but is invisible to the host and to every other running container.
  3. Parallel containers running the same agent. Two containers running the same agent today each have their own copy of the token. If one of them refreshes (OAuth refresh-token rotation), the other’s token can be invalidated server-side without warning — surfacing as a sudden 401 mid-session.

This roadmap item covers the long-term answer: a single source of truth for each auth axis, kept in sync across the host and every running jackin container in real time, by an opt-in jackin daemon.

The item is deliberately scoped beyond a single auth axis. It must cover, with one architecture:

  • GitHub CLI (gh)
  • Claude Code (file + macOS Keychain)
  • Codex (~/.codex/auth.json)
  • Amp (forthcoming)
  • any future authentication axis jackin’ adds (the credential source pattern evolution should plug in here too)

The current mode name sync describes “copy the host’s current state into the container at launch time.” That is not what “sync” means in any other context — sync conventionally implies continuous, bidirectional reconciliation. Today’s behaviour is closer to snapshot, forward, or mirror-on-launch.

Options for resolving the naming clash before this feature ships:

OptionToday’s mode renamed toNew live mode named
Aforwardsync (reclaim the word for the live axis)
Bsnapshotsync
Cmirrorlive-sync
D(keep sync)sync-live (but sync keeps misleading name)

Option A or B is preferable: they make the existing mode’s actual behaviour visible from the name and reserve the unmodified sync for what operators expect. Pre-release breaking-change policy (see AGENTS.md → “Project status: pre-release”) makes a rename across [github] / [claude] / [codex] configurations cheap. The implementation PR for this roadmap item should rename in lockstep with the daemon’s first ship; not beforehand (the rename without the live behaviour would be churn without payoff).

A decision belongs to the design pass that picks the daemon shape; this item flags the question rather than answering it.

Per auth axis, when the live mode is active:

  1. Host change → all running containers, near real time. Within a few seconds of a host-side gh auth login / claude /login / codex login, every running jackin container that opted in sees the new token. No restart required.
  2. In-container change → host AND other running containers. When an agent inside a container refreshes the token, that new token reaches the host’s authoritative store and any sibling running containers within seconds.
  3. Single refresh-token at a time, OAuth-safe. When an OAuth refresh-token rotation happens, only one party (the agent that triggered the refresh) holds the live grant; everyone else must read the new token before issuing the next API call. The architecture must avoid the parallel-agent invalidation cascade that bit operators on the launch-time sync behaviour today.
  4. Transparent storage abstraction. macOS host stores gh / claude tokens in the system Keychain; Linux stores them in plaintext under ~/.config/. The daemon must bridge those formats — read via gh auth token / security find-generic-password, write via security add-generic-password -U on macOS or atomic file write on Linux — without leaking keychain-only tokens as plaintext on disk.
  5. Operator-visible. The launch summary already names the auth source for each axis. The daemon must surface its state too — a status line or jackin auth status subcommand (proposed — does not exist today) showing which containers are subscribed, when each axis last reconciled, any pending conflicts. The Jackin Desktop Agent Hub consumes the same daemon state for Claude/Codex/Amp account cards instead of scraping provider files directly from the macOS app.
  6. Revocable and contained. Same opt-in / opt-out semantics the existing modes have. Operators must be able to switch a workspace or role to non-live forward / ignore and have the live channel torn down for that container.
┌────────────── host ──────────────┐ ┌─── running container ────┐
│ host token store │ │ agent-side store │
│ gh: Keychain or hosts.yml │ │ gh: hosts.yml │
│ claude: Keychain or .creds │ │ claude: .creds │
│ codex: auth.json │ │ codex: auth.json │
│ ↕ │ │ ↕ │
│ live-auth-sync adapter │ ←──→ │ jackin-auth-watcher │
│ (hosted by the jackin daemon) │ │ (in-container watcher) │
│ - polls gh auth token │ │ - inotify per axis │
│ - inotify per source file │ │ - small static binary │
│ - polling, with platform │ │ baked into construct │
│ change-event hooks where │ │ │
│ available │ │ │
│ - reconciles shared store │ │ │
└──────────────────────────────────┘ └──────────────────────────┘
↕ ↕
└─── shared store, flock-protected ─────┘
~/.jackin/auth-shared/<axis>/<file>
- atomic tmp+rename writes
- mtime + checksum for conflict detection
- bind-mounted into containers
  • Shared store at ~/.jackin/auth-shared/<axis>/. Single on-disk source of truth per axis. Atomic tmp+rename writes; flock to serialize concurrent writers.
  • live-auth-sync adapter — hosted by the jackin daemon. Not a separate process. The daemon owns the long-running lifecycle; this item adds one adapter against the daemon’s plug-in surface that watches host sources (poll + inotify on Linux; polling, with macOS Keychain change-event hooks where a stable public API exists — SecKeychainAddCallback is deprecated, so the macOS path is polling-only until a successor surfaces). Reconciles host → shared store, then shared store → host. Surfaces status to the CLI / TUI through the daemon’s control socket.
  • Container-side watcher — small static binary baked into the construct image. Inotify on the agent-store paths inside the container. On change, atomically push to the shared store bind-mount.
  • Mount strategy update. Today’s sync bind-mounts a per-container hosts.yml. Live mode bind-mounts the shared store path directly so all parties see the same file. The per-container path stays for forward / snapshot / ignore modes that don’t opt into live.
  • macOS Keychain bridge. Adapter writes tokens back to Keychain via security add-generic-password -U so the host user’s normal gh / claude workflow stays consistent. Never leak keychain-only tokens to plaintext files except inside the shared store + container.
  • Per-axis adapter trait. Each auth axis has its own source- store and on-disk shape. The daemon’s plug-in surface defines one trait per axis so adding Amp later is one adapter, not a rewrite.
  • Last-writer-wins by (mtime, checksum).
  • Agent-induced rotation always wins over a stale host poll. When the adapter’s poll cycle and an in-container refresh land in the same window, the in-container value is the source of truth (it’s the OAuth-grant that the server now considers current).
  • Detected conflict surfaces in jackin auth status (proposed subcommand — does not exist today) so the operator can see drift instead of having one side silently overwrite the other.

This is one of the first features that genuinely needs a long-running host-side jackin process. The shape of that process — lifecycle, install method, control socket, security posture, log redaction — is not decided here. It belongs to the umbrella jackin daemon roadmap item, which exists so each reactive feature plugs into one common daemon rather than each inventing its own.

This live-sync item depends on jackin daemon Phase 1 (empty daemon — lifecycle and control socket) shipping first. Live sync then adds a per-axis adapter against the daemon’s plug-in surface: one adapter for gh, one for Claude, one for Codex, future ones for Amp and any new auth axis. The adapter trait shape is described under the daemon roadmap item. Desktop should treat these adapters as its account-status source whenever they exist; direct CLI/file checks are fallback diagnostics only.

Per AGENTS.md “Never mutate the host machine silently”: the live-sync adapter writes to host-side state. Each write is opt-in via the workspace’s chosen mode (live / sync / forward / ignore) and surfaces in the daemon’s status output and the launch summary.

  • macOS Keychain writes. When an in-container refresh produces a new token, the adapter writes it back to the host Keychain via security add-generic-password -U so the host user’s normal gh / claude workflow keeps working. The operator authorizes the write by enabling live mode for the axis; the daemon’s status output names every host write as it happens.
  • Plaintext shared-store writes. The adapter writes the current axis token to ~/.jackin/auth-shared/<axis>/<file> (atomic tmp+rename, flock-protected). Same opt-in contract: live mode is the only mode that produces these files; other modes leave the path empty.
  • No host repository / dotfile mutations. The adapter never touches ~/.gitconfig, ~/.ssh/, ~/.config/gh/hosts.yml outside the operator’s own gh writes, or any user repo’s .git/config. Read-only access to host source files (e.g. gh auth token) is fine; writes target only the Keychain entry the operator’s host already owns and the jackin-private shared store.
  • Opt-out path. Switching the workspace or role to a non- live mode tears down the adapter for that axis and leaves host state at whatever the most-recent live-mode reconcile produced; no further writes occur.
  • SSH-key forwarding stays excluded. Authentication documents why.
  • Cross-host sync (different operator machines sharing one store). The daemon is per-operator-user, per-host.
  • Distributed conflict resolution beyond last-writer-wins.

When this item ships, each per-axis auth doc and the auth overview must be updated together so operators understand the new mode’s semantics and the rename. The implementing PR series should land docs updates in lockstep with the daemon.

  • jackin daemon — umbrella item that introduces the long-running jackin process this feature depends on. Lifecycle, install, control socket, security posture, log redaction are decided there.
  • GitHub CLI authentication strategy — current sync mode lives here; “Bidirectional shared-store sync” was previously listed as a follow-up under that item. This dedicated roadmap item replaces that follow-up.
  • Reliable Claude authentication strategy — Claude axis design notes; live sync becomes the long-term answer to the concurrent-session token-drift concerns.
  • Credential source pattern — future unified credential resolver. The daemon’s per-axis adapter trait should be expressible inside the credential source pattern.
  • Jackin Desktop Agent Hub — native account/status surface that reads live auth and quota state from the daemon first.