multica-ai/multica · error · Error

daemon profile is not resolved yet; token sync skipped

Error message

daemon profile is not resolved yet; token sync skipped

What it means

Thrown by prepareHermesHome when os.Chmod(hermesHome, 0o700) fails after MkdirAll. The chmod is intentional on reuse: MkdirAll leaves an existing directory's mode alone, and the derived config written into the overlay can hold inline api_key secrets, so perms are re-tightened every prepare. A chmod failure means the daemon cannot guarantee the overlay is private, so it fails the task.

Source

Thrown at apps/desktop/src/main/daemon-manager.ts:656

 *   id matches the caller, reuse it — minting fresh on every launch would
 *   accumulate garbage in the user's tokens page.
 * - On user mismatch (or first run) call POST /api/tokens with the JWT to
 *   mint a fresh PAT, overwriting any stale cached PAT. This is the critical
 *   path: without it, a previous user's PAT would be used by a new session.
 * - If the caller happens to pass a PAT directly, write it through.
 * - When we mint fresh and a daemon is already running, restart it so the
 *   new credentials take effect (the Go daemon reads config at startup).
 */
async function syncToken(
  tokenFromRenderer: string,
  userId: string,
): Promise<void> {
  const active = await ensureActiveProfile();
  if (!active) {
    // Writing here would land the token and server_url in the user's default
    // CLI config. The renderer awaits setTargetApiUrl before calling this, so
    // reaching this branch is a real error rather than a normal startup race.
    throw new Error("daemon profile is not resolved yet; token sync skipped");
  }
  const config = await readProfileConfig(active.name);
  const previousUserId = await readProfileUserId(active.name);
  const userChanged = Boolean(previousUserId) && previousUserId !== userId;
  const sameUserWithCachedPat =
    !userChanged &&
    previousUserId === userId &&
    typeof config.token === "string" &&
    config.token.startsWith("mul_");

  let finalToken: string;
  if (tokenFromRenderer.startsWith("mul_")) {
    finalToken = tokenFromRenderer;
  } else if (sameUserWithCachedPat) {
    finalToken = config.token as string;
  } else {
    try {
      finalToken = await mintPat(tokenFromRenderer);

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. chown the existing hermes-home (and the env-root) to the daemon's current user, then retry the task.
  2. If chmod is unsupported by the mount, move env-roots to a POSIX-compliant local filesystem.
  3. Ensure only one daemon/GC process operates on an env-root at a time to rule out concurrent deletion.
  4. As a last resort remove the stale per-task env-root so prepare recreates it fresh with correct ownership.

Example fix

# before: overlay owned by old daemon user → chmod fails
$ ls -ld /var/lib/app/envs/task-9/hermes-home
drwxr-xr-x 1 olduser olduser ...

# after
$ chown -R daemonuser:daemongroup /var/lib/app/envs/task-9
Defensive patterns

Strategy: try-catch

Validate before calling

if fi, err := os.Stat(hermesHome); err == nil {
    if stat, ok := fi.Sys().(*syscall.Stat_t); ok && uint(stat.Uid) != os.Getuid() {
        return fmt.Errorf("hermes-home owned by uid %d, daemon runs as %d — chown first", stat.Uid, os.Getuid())
    }
}

Try / catch

if err := prepareHermesHome(...); err != nil {
    if strings.Contains(err.Error(), "chmod hermes-home dir") {
        // ownership mismatch or unsupported fs: chown tree or move env-root to local POSIX disk
    }
}

Prevention

When it happens

Trigger: prepareHermesHome reuses an existing hermes-home whose owner is not the daemon user (chmod requires ownership or CAP_FOWNER), or the filesystem does not support chmod (some FUSE/NFS mounts), or the dir was removed concurrently between MkdirAll and Chmod.

Common situations: Daemon restarted under a different UID while old task env-roots persist; env-root on NFS with root-squash mapping; a concurrent GC deleting the env-root mid-prepare.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/b5e7c48b90077983. Report an issue: GitHub.