{"record":{"id":"24dd8f4460334d5b","repo":"nanocoai/nanoclaw","slug":"agent-route-failed-to-inspect-source-outbox-dir","errorCode":null,"errorMessage":"agent-route: failed to inspect source outbox dir","messagePattern":"agent-route: failed to inspect source outbox dir","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/modules/agent-to-agent/agent-route.ts","lineNumber":89,"sourceCode":"      sourceMsgId: source.messageId,\n      sourceDir,\n    });\n    return [];\n  }\n\n  let realSourceDir: string;\n  try {\n    const sourceDirStat = fs.lstatSync(sourceDir);\n    if (!sourceDirStat.isDirectory() || sourceDirStat.isSymbolicLink()) {\n      log.warn('agent-route: rejecting unsafe source outbox dir', {\n        sourceMsgId: source.messageId,\n        sourceDir,\n      });\n      return [];\n    }\n    realSourceDir = fs.realpathSync(sourceDir);\n  } catch (err) {\n    log.warn('agent-route: failed to inspect source outbox dir', {\n      sourceMsgId: source.messageId,\n      sourceDir,\n      err,\n    });\n    return [];\n  }\n\n  // Target-side containment — shared with the channel-inbound path. A\n  // compromised target agent can write inside its own session dir, so it could\n  // pre-place `inbox` (or `inbox/<future-msgId>`) as a symlink pointing\n  // anywhere host-writable; ensureContainedInboxDir refuses the symlink before\n  // any copy lands outside the sandbox (#2828, CWE-59).\n  const inboxRoot = path.join(sessionDir(target.agentGroupId, target.sessionId), 'inbox');\n  const targetInboxDir = ensureContainedInboxDir(inboxRoot, target.messageId, {\n    targetGroup: target.agentGroupId,\n    targetSession: target.sessionId,\n    targetMsgId: target.messageId,\n  });","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/modules/agent-to-agent/agent-route.ts#L71-L107","documentation":"While forwarding attachments in an agent-to-agent routed message, the host failed to inspect (realpath) the source session's outbox attachment directory. realpathSync threw — typically ENOENT because the directory no longer exists, or EACCES/EMMLINK on pathological mounts. The failure is non-fatal: forwarding returns no files and the message goes on without attachments.","triggerScenarios":"routeAgentMessage forwards a message whose source messageId references attachments under data/v2-sessions/<source-session>/attachments; the source session directory was deleted/swept before forwarding ran, or a mount/permission change made the path unreadable.","commonSituations":"Session retention sweep racing with a delayed a2a forward; manually deleted session dirs; group folder moved between host restarts; NFS/sync mounts with flaky realpath behavior.","solutions":["Check that the source session dir data/v2-sessions/<group>/<session>/ still exists when the forward runs (sweep ordering)","Verify the messageId being forwarded actually has attachments recorded in the source outbox DB","If mounts are involved, confirm the host process has read+execute on every path component","Rate-limit or serialize session cleanup so in-flight forwards finish first"],"exampleFix":"// before\nrealSourceDir = fs.realpathSync(sourceDir);\n// after\nrealSourceDir = fs.existsSync(sourceDir) ? fs.realpathSync(sourceDir) : null;\nif (!realSourceDir) { log.warn('source outbox missing', { sourceDir }); return []; }","handlingStrategy":"validation","validationCode":"const exists = fs.existsSync(sourceDir) && fs.statSync(sourceDir).isDirectory();\nif (!exists) return []; // skip forwarding","typeGuard":null,"tryCatchPattern":"catch (err) { if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []; throw err; }","preventionTips":["Serialize session cleanup behind in-flight forwards","Log sourceDir alongside messageId to correlate with sweep timing"],"tags":["filesystem","agent-to-agent","attachments","enoent"],"backgroundTag":"file-not-found","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}