thedotmack/claude-mem · error

Refusing inject write to a file this writer does not own

Error message

Refusing inject write to a file this writer does not own: ${path.basename(resolved)}

What it means

assertSafeInjectPath enforces that this writer only ever overwrites its own inject log file. Even inside the correct directory, a target whose basename is not INJECT_LOG_BASENAME is rejected, preventing accidental clobbering of sibling files in the agent memory/log directory.

Solutions

  1. Use injectLogPath(agentDataRoot, agentId) to derive the exact owned filename instead of constructing it manually.
  2. If you need to write a different file, use a general-purpose file writer rather than this guarded inject-log writer.
  3. Check the constant INJECT_LOG_BASENAME and make sure your path uses exactly that basename (no suffixes).

Example fix

// before
writeFileAtomic(path.join(logDir, 'inject.md.tmp'), data);
// after
writeFileAtomic(injectLogPath(root, agentId), data);
Defensive patterns

Strategy: validation

Validate before calling

if (path.basename(path.resolve(target)) !== INJECT_LOG_BASENAME) {
  throw new Error('writer only owns ' + INJECT_LOG_BASENAME);
}

Try / catch

try {
  assertSafeInjectPath(root, agentId, target);
  writeFileAtomic(target, data);
} catch (err) {
  if (String(err).startsWith('Refusing inject write')) logger.warn('Not the owned file', { target });
}

Prevention

When it happens

Trigger: refreshSeatIndex or ensureIndexLogDir targets a file inside memory/log other than INJECT_LOG_BASENAME — e.g. 'summary.md', 'notes.log', or a date-suffixed filename built by the caller.

Common situations: Code generalized to write arbitrary log files reuses this guard; filename constants were renamed in one place but not another; callers append suffixes like '.bak' to the path.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

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

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) {
    return { changed: false, filePath };
  }
  writeFileAtomic(filePath, contents);
  return { changed: true, filePath };
}

View on GitHub (pinned to d8bc9755e7)