thedotmack/claude-mem · error

Refusing write to profile.md

Error message

Refusing write to profile.md

What it means

assertSafeInjectPath validates a destination path before the memory writer writes to it. profile.md is a user-owned agent file that this writer must never modify, so any write target named profile.md (case-insensitive) is rejected outright.

Solutions

  1. Write to INJECT_LOG_BASENAME (the owned log file) inside agents/<agentId>/memory/log instead of profile.md.
  2. Update the calling code so profile content is handled by the profile-specific writer, not the inject-log writer.
  3. Check the resolved basename before calling and skip the write when it is profile.md.

Example fix

// before
writeInject(root, agentId, path.join(dir, 'profile.md'), data);
// after
writeInject(root, agentId, injectLogPath(root, agentId), data);
Defensive patterns

Strategy: validation

Validate before calling

if (path.basename(path.resolve(target)).toLowerCase() === 'profile.md') {
  throw new Error('use the profile writer, not the inject writer');
}

Try / catch

try {
  assertSafeInjectPath(root, agentId, target);
  writeFileAtomic(target, data);
} catch (err) {
  logger.error('Inject write rejected', { target }, err);
}

Prevention

When it happens

Trigger: refreshSeatIndex or ensureIndexLogDir computes a write path whose basename is profile.md — e.g. a caller passes the profile file instead of the inject-log basename into the write path guard.

Common situations: A caller confuses the inject log with the agent profile file; refactored code reuses the writer for profile updates; a dynamic filename ends up as 'Profile.md'.

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/2672549ca833a216. Report an issue: GitHub.

Appendix: source

Thrown at src/services/integrations/grok-bot-index-format.ts:172

    .join('\n');
}

export function shouldRewriteInject(existingContents: string, nextContents: string): boolean {
  return factBlock(existingContents) !== factBlock(nextContents);
}

export function injectLogPath(agentDataRoot: string, agentId: string): string {
  if (!AGENT_ID_RE.test(agentId)) {
    throw new Error(`Refusing inject path for non-UUID agent id: ${agentId}`);
  }
  return path.join(agentDataRoot, 'agents', agentId, 'memory', 'log', INJECT_LOG_BASENAME);
}

export function assertSafeInjectPath(agentDataRoot: string, agentId: string, filePath: string): void {
  const expectedDir = path.resolve(path.join(agentDataRoot, 'agents', agentId, 'memory', 'log'));
  const resolved = path.resolve(filePath);
  if (path.basename(resolved).toLowerCase() === 'profile.md') {
    throw new Error('Refusing write to profile.md');
  }
  if (path.dirname(resolved) !== expectedDir) {
    throw new Error('Refusing inject write outside agent memory/log');
  }
  if (path.basename(resolved) !== INJECT_LOG_BASENAME) {
    throw new Error(`Refusing inject write to a file this writer does not own: ${path.basename(resolved)}`);
  }
}

function writeFileAtomic(filePath: string, contents: string): void {
  mkdirSync(path.dirname(filePath), { recursive: true });
  const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
  writeFileSync(tmp, contents, 'utf8');
  renameSync(tmp, filePath);
}

export function writeFileIfChanged(filePath: string, contents: string): { changed: boolean; filePath: string } {
  if (existsSync(filePath) && readFileSync(filePath, 'utf8') === contents) {

View on GitHub (pinned to d8bc9755e7)