{"record":{"id":"b5e7c48b90077983","repo":"multica-ai/multica","slug":"daemon-profile-is-not-resolved-yet-token-sync-ski","errorCode":null,"errorMessage":"daemon profile is not resolved yet; token sync skipped","messagePattern":"daemon profile is not resolved yet; token sync skipped","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/src/main/daemon-manager.ts","lineNumber":656,"sourceCode":" *   id matches the caller, reuse it — minting fresh on every launch would\n *   accumulate garbage in the user's tokens page.\n * - On user mismatch (or first run) call POST /api/tokens with the JWT to\n *   mint a fresh PAT, overwriting any stale cached PAT. This is the critical\n *   path: without it, a previous user's PAT would be used by a new session.\n * - If the caller happens to pass a PAT directly, write it through.\n * - When we mint fresh and a daemon is already running, restart it so the\n *   new credentials take effect (the Go daemon reads config at startup).\n */\nasync function syncToken(\n  tokenFromRenderer: string,\n  userId: string,\n): Promise<void> {\n  const active = await ensureActiveProfile();\n  if (!active) {\n    // Writing here would land the token and server_url in the user's default\n    // CLI config. The renderer awaits setTargetApiUrl before calling this, so\n    // reaching this branch is a real error rather than a normal startup race.\n    throw new Error(\"daemon profile is not resolved yet; token sync skipped\");\n  }\n  const config = await readProfileConfig(active.name);\n  const previousUserId = await readProfileUserId(active.name);\n  const userChanged = Boolean(previousUserId) && previousUserId !== userId;\n  const sameUserWithCachedPat =\n    !userChanged &&\n    previousUserId === userId &&\n    typeof config.token === \"string\" &&\n    config.token.startsWith(\"mul_\");\n\n  let finalToken: string;\n  if (tokenFromRenderer.startsWith(\"mul_\")) {\n    finalToken = tokenFromRenderer;\n  } else if (sameUserWithCachedPat) {\n    finalToken = config.token as string;\n  } else {\n    try {\n      finalToken = await mintPat(tokenFromRenderer);","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/apps/desktop/src/main/daemon-manager.ts#L638-L674","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["chown the existing hermes-home (and the env-root) to the daemon's current user, then retry the task.","If chmod is unsupported by the mount, move env-roots to a POSIX-compliant local filesystem.","Ensure only one daemon/GC process operates on an env-root at a time to rule out concurrent deletion.","As a last resort remove the stale per-task env-root so prepare recreates it fresh with correct ownership."],"exampleFix":"# before: overlay owned by old daemon user → chmod fails\n$ ls -ld /var/lib/app/envs/task-9/hermes-home\ndrwxr-xr-x 1 olduser olduser ...\n\n# after\n$ chown -R daemonuser:daemongroup /var/lib/app/envs/task-9","handlingStrategy":"try-catch","validationCode":"if fi, err := os.Stat(hermesHome); err == nil {\n    if stat, ok := fi.Sys().(*syscall.Stat_t); ok && uint(stat.Uid) != os.Getuid() {\n        return fmt.Errorf(\"hermes-home owned by uid %d, daemon runs as %d — chown first\", stat.Uid, os.Getuid())\n    }\n}","typeGuard":null,"tryCatchPattern":"if err := prepareHermesHome(...); err != nil {\n    if strings.Contains(err.Error(), \"chmod hermes-home dir\") {\n        // ownership mismatch or unsupported fs: chown tree or move env-root to local POSIX disk\n    }\n}","preventionTips":["Run the daemon under one stable UID across restarts.","Keep env-roots on local filesystems that support chmod.","On daemon user changes, chown all existing env-roots as part of migration."],"tags":["go","hermes","execenv","filesystem","permissions","secrets"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}