can1357/oh-my-pi · error

${name} exceeds the ${SHARPSHOOTER_MAX_FILE_LINES}-line limi

Error message

${name} exceeds the ${SHARPSHOOTER_MAX_FILE_LINES}-line limit

What it means

Each replacement file's content, after secret redaction, is line-counted (newlines counted by scanning char codes). If a file exceeds SHARPSHOOTER_MAX_FILE_LINES lines, this error aborts consolidation so memory files stay small and useful.

Source

Thrown at packages/coding-agent/src/sharpshooter/consolidate.ts:262

	const files: ReplacementFile[] = [];
	for (const item of args.files) {
		if (!item || typeof item !== "object" || !("name" in item) || !("content" in item)) {
			throw new Error("replace_memory_files contains an invalid file entry");
		}
		const name = item.name;
		const rawContent = item.content;
		if (!isMemoryFileName(name) || typeof rawContent !== "string") {
			throw new Error("replace_memory_files contains an invalid file entry");
		}
		if (seen.has(name)) throw new Error(`replace_memory_files contains duplicate ${name}`);
		seen.add(name);
		const redacted = redactSecrets(rawContent);
		let lines = redacted.length > 0 ? 1 : 0;
		for (let index = 0; index + 1 < redacted.length; index++) {
			if (redacted.charCodeAt(index) === 10) lines += 1;
		}
		if (lines > SHARPSHOOTER_MAX_FILE_LINES) {
			throw new Error(`${name} exceeds the ${SHARPSHOOTER_MAX_FILE_LINES}-line limit`);
		}
		files.push({ name, content: redacted });
	}
	const totalChars = files.reduce((sum, file) => sum + file.content.trim().length, 0);
	if (totalChars === 0 && SHARPSHOOTER_MEMORY_FILES.some(name => currentFiles[name].trim().length > 0)) {
		throw new Error("replace_memory_files returned all-empty content; refusing to wipe memory files");
	}
	return files;
}

function isMemoryFileName(value: unknown): value is SharpshooterMemoryFile {
	return typeof value === "string" && (SHARPSHOOTER_MEMORY_FILES as readonly string[]).includes(value);
}

async function applyReplacementFiles(bankDir: string, files: readonly ReplacementFile[]): Promise<void> {
	const staged = files.map(file => ({
		...file,
		tempPath: path.join(bankDir, `.${file.name}.${process.pid}.${crypto.randomUUID()}.tmp`),

View on GitHub (pinned to 9690622007)

Solutions

  1. Condense the file content to a terse summary under the line limit.
  2. Split content across the other allowed memory files where appropriate.
  3. Move detail out to project files and keep only pointers in memory.
  4. Retry with a stronger model or prompt emphasizing the per-file line budget.

Example fix

// before: 400-line dump
content: <full session log>
// after: compact summary
content: "- auth: uses JWT in src/auth.ts\n- db: sqlite via bun:sqlite"
Defensive patterns

Strategy: validation

Validate before calling

const lineCount = (s: string) => (s.length ? 1 + [...s].filter(c => c === "\n").length : 0);
files.forEach(f => { if (lineCount(f.content) > MAX_LINES) throw new Error(`${f.name} too long`); });

Type guard

null

Try / catch

try {
  files = parseReplacementFiles(call, currentFiles);
} catch (err) {
  if (String(err).includes("-line limit")) {
    // retry with instruction to condense
  }
}

Prevention

When it happens

Trigger: A model writes an overly verbose memory file whose redacted content contains more than SHARPSHOOTER_MAX_FILE_LINES newlines.

Common situations: Models pasting whole conversation logs into memory files; accumulating changelogs instead of summaries; low-effort models ignoring the length instruction in the tool description.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/56bb05e0f65ec175. Report an issue: GitHub.