nanocoai/nanoclaw · warning

agent-route: failed to inspect source outbox dir

Error message

agent-route: failed to inspect source outbox dir

What it means

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.

Source

Thrown at src/modules/agent-to-agent/agent-route.ts:89

      sourceMsgId: source.messageId,
      sourceDir,
    });
    return [];
  }

  let realSourceDir: string;
  try {
    const sourceDirStat = fs.lstatSync(sourceDir);
    if (!sourceDirStat.isDirectory() || sourceDirStat.isSymbolicLink()) {
      log.warn('agent-route: rejecting unsafe source outbox dir', {
        sourceMsgId: source.messageId,
        sourceDir,
      });
      return [];
    }
    realSourceDir = fs.realpathSync(sourceDir);
  } catch (err) {
    log.warn('agent-route: failed to inspect source outbox dir', {
      sourceMsgId: source.messageId,
      sourceDir,
      err,
    });
    return [];
  }

  // Target-side containment — shared with the channel-inbound path. A
  // compromised target agent can write inside its own session dir, so it could
  // pre-place `inbox` (or `inbox/<future-msgId>`) as a symlink pointing
  // anywhere host-writable; ensureContainedInboxDir refuses the symlink before
  // any copy lands outside the sandbox (#2828, CWE-59).
  const inboxRoot = path.join(sessionDir(target.agentGroupId, target.sessionId), 'inbox');
  const targetInboxDir = ensureContainedInboxDir(inboxRoot, target.messageId, {
    targetGroup: target.agentGroupId,
    targetSession: target.sessionId,
    targetMsgId: target.messageId,
  });

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. Check that the source session dir data/v2-sessions/<group>/<session>/ still exists when the forward runs (sweep ordering)
  2. Verify the messageId being forwarded actually has attachments recorded in the source outbox DB
  3. If mounts are involved, confirm the host process has read+execute on every path component
  4. Rate-limit or serialize session cleanup so in-flight forwards finish first

Example fix

// before
realSourceDir = fs.realpathSync(sourceDir);
// after
realSourceDir = fs.existsSync(sourceDir) ? fs.realpathSync(sourceDir) : null;
if (!realSourceDir) { log.warn('source outbox missing', { sourceDir }); return []; }
Defensive patterns

Strategy: validation

Validate before calling

const exists = fs.existsSync(sourceDir) && fs.statSync(sourceDir).isDirectory();
if (!exists) return []; // skip forwarding

Try / catch

catch (err) { if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []; throw err; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/24dd8f4460334d5b. Report an issue: GitHub.