---
title: "Live Bidirectional Auth Sync"
---
import { Aside } from '@astrojs/starlight/components'
import RepoFile from '../../../../components/RepoFile.astro'


**Status**: Open — design proposal

## Problem

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](/reference/roadmap/credential-source-pattern/)
  evolution should plug in here too)

## Naming reconsideration

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:

| Option | Today's mode renamed to | New live mode named |
|---|---|---|
| **A** | `forward` | `sync` (reclaim the word for the live axis) |
| **B** | `snapshot` | `sync` |
| **C** | `mirror` | `live-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 <RepoFile path="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.

## What "live sync" must do

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.

## Architecture sketch

```
┌────────────── 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
```

### Components to build

- **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](/reference/roadmap/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.

### Conflict resolution

- **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.

### Why this depends on the daemon

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](/reference/roadmap/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.

## Host-side effects

Per <RepoFile path="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.

## Out of scope for this item

- SSH-key forwarding stays excluded. [Authentication](/guides/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.

## How this changes the per-axis pages

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.

## Related work

- [jackin daemon](/reference/roadmap/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](/reference/roadmap/github-cli-auth-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](/reference/roadmap/claude-auth-strategy/)
  — Claude axis design notes; live sync becomes the long-term
  answer to the concurrent-session token-drift concerns.
- [Credential source pattern](/reference/roadmap/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](/reference/roadmap/jackin-desktop-agent-hub/)
  — native account/status surface that reads live auth and quota
  state from the daemon first.
