Skip to content

Multica runtime identity: the daemon.id hijack

Applies to: Any Multica crew host where ~/.multica/daemon.id may have been copied or shared from another machine.

Multica keys a runtime's identity on the contents of ~/.multica/daemon.id, not on the hostname. Two machines carrying the same daemon.id are a single runtime record to the server, and the last daemon to start silently claims it — rewriting the record's name/device_info and receiving all agent work dispatched to it. No conflict error, no warning, no log line on either side.

The incident (2026-08-02, OBJ-2049 and five others)

Both Objectuve-Pro (a second Mac) and joshuas-mbp-2.lan (the real crew runtime) carried the identical daemon.id 019db58e-51f8-7dcb-b9e7-580bdabd4636 — the file was dated Apr 22 on both, i.e. ~/.multica/ had at some point been copied wholesale between the machines. All 12 Objectuve agents bind to runtime bf3c31e7-4a7e-452d-98c7-d0e88beda4dd, whose bound daemon_id field holds that same value.

Time (UTC)Event
10:33A daemon restart on Objectuve-Pro claims bf3c31e7, renaming it Claude (Objectuve-Pro.local) · 2.1.173
10:39:54First 401 OAuth access token has been revoked — crew work is now landing on a laptop whose claude CLI login expired 2026-05-25
10:39 → 16:0920 × 401 across 10 distinct tasks, all failure_reason=agent_error.provider_auth_or_access
16:22:50A daemon restart on joshuas-mbp-2.lan flips the record back to Claude (joshuas-mbp-2.lan) · 2.1.220
16:27 → 16:29Rerun of OBJ-2049 completes cleanly — fix confirmed

Six issues took collateral damage: OBJ-2049, OBJ-1982, OBJ-2058, OBJ-2057, OBJ-1997, OBJ-1993 (plus OBJ-2044, already blocked for unrelated reasons).

The name drift is itself the tell. multica-github-auth.md records this runtime as Claude (Joshuas-MacBook-Pro-2.local); it now reads Claude (joshuas-mbp-2.lan). A runtime whose displayed name changes without anyone renaming anything has been re-registered by a different daemon.

Why the obvious diagnosis is the wrong one

The 401 wording says revoked, which reads like a credential problem, and there genuinely was one: Objectuve-Pro's Claude Code login had been dead for months. Two further real-but-downstream details make the credential story even more convincing:

  • Claude Code session env inheritance (CLAUDE_CODE_HOST_SESSION_ID, CLAUDE_CODE_SDK_HAS_HOST_AUTH_REFRESH, ANTHROPIC_BASE_URL, …) turns what would otherwise surface as "Not logged in · Please run /login" into "revoked" when the daemon was started from inside a Claude Code session.
  • The runtime heartbeats normally throughout. last_seen stays current, status stays online. Heartbeat is not auth — a runtime can be perfectly healthy at the transport layer and fail every single agent run.

None of it would have mattered if the runtime hadn't been hijacked. Check runtime ownership before debugging credentials — otherwise you re-login a machine that was never supposed to be serving the work.

Diagnosis

One command settles it. Compare the local file against the runtime the agents are bound to:

bash
cat ~/.multica/daemon.id
multica agent list --output json | jq -r '.[].runtime_id' | sort -u
multica runtime list --output json | jq -r '.[] | "\(.id) \(.daemon_id) \(.name)"'

If the local daemon.id equals the daemon_id of a runtime you did not intend this machine to own, you have a hijack (or are one restart away from one). A null daemon_id on some other runtime in the list is expected and benign — it just means that runtime has no daemon bound to it yet, not a second incident.

To prove which machine actually owns which runtime, stop the local daemon and re-list. Runtimes that stay online belong to the other machine; ones that flip offline were genuinely local:

bash
multica daemon stop
multica runtime list

Applied here, that A/B was run from Objectuve-Pro — the machine that should not have owned the runtime — and was unambiguous:

RuntimeBefore stopAfter stopVerdict
bf3c31e7 Claudeonlineonlinejoshuas-mbp-2.lan's — was hijacked
18e011d1 Opencodeonlineonlinejoshuas-mbp-2.lan's — never hijacked, confirms baseline ownership
8c621a62 Hermesonlineofflinegenuinely local
3e5130d2 Geminionlineofflinegenuinely local

Supporting evidence lives in ~/.multica/daemon.log (the 401s and their failure_reason) and ~/.multica/daemon.pid (mtime = the hijacking restart). Note that ~/.multica/config.json holds only app_url / server_url / token / workspace_idno identity key. Identity is daemon.id alone.

Fix

On the machine that should not own the runtime, stop the daemon and retire the duplicated identity file so a future start registers fresh instead of hijacking:

bash
multica daemon stop
mv ~/.multica/daemon.id ~/.multica/daemon.id.disabled-dupe

Then rerun the affected issues from any machine:

bash
multica issue rerun <issue-id>

A subsequent multica daemon start on that machine now registers a new, unbound runtime. That runtime will sit idle — correct and intended, since no agent is bound to it. Do not "fix" the idleness by restoring the old daemon.id.

multica --profile <name> … isolates config and daemon state per profile and is the cleaner option if a machine legitimately needs to run a second daemon.

Prevention

  • Never copy ~/.multica/ between machines. Copy config.json alone if you need to carry credentials over, and let each machine generate its own daemon.id on first start.
  • Before starting a daemon on a machine that hasn't run one recently, check cat ~/.multica/daemon.id against the bound runtime first.
  • Treat a runtime name change as an incident signal, not cosmetic drift.
  • Confirming a fix requires a completed run, not a green last_seen. Use multica issue runs <issue-id> and look for a completed row.

Open gap

Nothing detects this automatically. There is still no alerting on consecutive agent-run failures or on runner login health — the same gap flagged after the 2026-06-30 → 07-03 outage (~3 days unnoticed, 401 Invalid authentication credentials, terminal variant "Not logged in · Please run /login"). This incident ran ~6 hours before a human noticed a stalled issue. A crew-side check that fires on N consecutive agent_error.provider_auth_or_access failures, or a server-side conflict warning when a daemon.id re-registers from a different device_info, would have caught both.


Last updated: 2026-08-03

Loading…