thedotmack/claude-mem · error

Refusing awareness write outside agent memory/log

Error message

Refusing awareness write outside agent memory/log

What it means

assertSafeAwarenessLogPath() validates that awareness writes from the Grok bot pusher stay inside the agent's own memory/log directory (agentDataRoot/agents/<agentId>/memory/log). It resolves both paths and throws if the target escapes that root, preventing awareness appends from touching arbitrary filesystem locations.

Solutions

  1. Build the log path as path.join(agentDataRoot, 'agents', agentId, 'memory', 'log', filename) so it is anchored in the expected root.
  2. Verify the agentId used to compute the path matches the agentId passed to notifyGrokBotAwareness.
  3. Strip or reject any ../ traversal segments from the logPath before calling.

Example fix

// before: path not anchored to the agent's memory/log
const logPath = path.join(agentDataRoot, 'agents', rel);

// after
const logPath = path.join(agentDataRoot, 'agents', agentId, 'memory', 'log', 'awareness.md');
Defensive patterns

Strategy: validation

Validate before calling

import path from 'path';
function isInsideAgentLogRoot(agentDataRoot: string, agentId: string, logPath: string): boolean {
  const expectedRoot = path.resolve(path.join(agentDataRoot, 'agents', agentId, 'memory', 'log'));
  const resolved = path.resolve(logPath);
  return resolved === expectedRoot || resolved.startsWith(expectedRoot + path.sep);
}

Try / catch

try {
  notifyGrokBotAwareness(agentDataRoot, agentId, logPath, line);
} catch (err) {
  if (err instanceof Error && err.message.includes('outside agent memory/log')) {
    // rebuild logPath from agentDataRoot/agents/<agentId>/memory/log and retry
  } else throw err;
}

Prevention

When it happens

Trigger: notifyGrokBotAwareness() calls assertSafeAwarenessLogPath(agentDataRoot, agentId, logPath) with a logPath that resolves outside the per-agent memory/log dir — e.g. path built with ../ segments, an absolute path elsewhere, or an agentId that does not match the one baked into the log path.

Common situations: Caller constructs the log path from concatenated IDs or user-supplied agent names; agentId renamed/migrated so the stored log path no longer matches; relative vs absolute path mixing; symlinks resolving outside the root.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/9ea275a7a90f3867. Report an issue: GitHub.

Appendix: source

Thrown at src/services/integrations/GrokBotAwarenessPusher.ts:101

  if (line.length <= MAX_LINE_CHARS) return line;
  return `${line.slice(0, MAX_LINE_CHARS - 1)}…`;
}

export function awarenessLineBody(line: string): string {
  const idx = line.indexOf(AWARENESS_TAG);
  return idx >= 0 ? line.slice(idx).trim() : line.trim();
}

export function grokBotAwarenessLogPath(agentDataRoot: string, agentId: string, now: Date = new Date()): string {
  const yearMonth = now.toISOString().slice(0, 7);
  return path.join(agentDataRoot, 'agents', agentId, 'memory', 'log', `${yearMonth}.md`);
}

function assertSafeAwarenessLogPath(agentDataRoot: string, agentId: string, logPath: string): void {
  const expectedRoot = path.resolve(path.join(agentDataRoot, 'agents', agentId, 'memory', 'log'));
  const resolved = path.resolve(logPath);
  if (!resolved.startsWith(expectedRoot + path.sep) && resolved !== expectedRoot) {
    throw new Error('Refusing awareness write outside agent memory/log');
  }
  if (path.basename(resolved) === 'profile.md') {
    throw new Error('Refusing awareness write to profile.md');
  }
}

export function appendAwarenessLineAtomic(logPath: string, line: string): boolean {
  const dir = path.dirname(logPath);
  mkdirSync(dir, { recursive: true });

  const existing = existsSync(logPath) ? readFileSync(logPath, 'utf8') : '';
  const incomingBody = awarenessLineBody(line);
  const alreadyPresent = existing
    .split('\n')
    .some(existingLine => existingLine.trim() && awarenessLineBody(existingLine) === incomingBody);
  if (alreadyPresent) {
    return false;
  }

View on GitHub (pinned to d8bc9755e7)