can1357/oh-my-pi · error
Missing hashline snapshot tag for
Error message
Missing hashline snapshot tag for ${sectionPath}; use `${HL_FILE_PREFIX}${sectionPath}${HL_FILE_HASH_SEP}tag${HL_FILE_SUFFIX}` from your latest read/search output. To create a new file, use the write tool. What it means
Hashline edits carry a 4-hex content snapshot tag in the section header proving the model read the current file version. applyPreviewEdits()/apply() throws this message when the header has no tag (fileHash undefined) and hash validation is not skipped — the library refuses to apply edits against an unverified snapshot. The message points at the exact header syntax required and suggests the write tool for new files.
Solutions
- Copy the exact `@@@ <path>:<tag>` header from the latest read/grep output and re-issue the edit.
- If the file is new/empty, use the write tool instead of the edit tool.
- Confirm the tag separator (:tag before the closing suffix) survived into the emitted section — parser mismatches surface as 'no tag'.
Example fix
// before `@@@ src/utils/diff.ts\n- old line\n+ new line` // after `@@@ src/utils/diff.ts:9f3c\n- old line\n+ new line` // tag copied from latest read output
Defensive patterns
Strategy: validation
Validate before calling
// extract tag from latest read output and require it in the header
const header = `@@@ ${path}:${latestSnapshotTag}`;
if (!/:[0-9a-f]{4}/.test(header)) {
throw new Error(`Refusing to edit: missing snapshot tag in header '${header}'`);
} Type guard
function hasSnapshotTag(section: string): boolean {
const header = section.split('\n', 1)[0] ?? '';
return /:[0-9a-f]{4}\s*$/.test(header);
} Try / catch
try {
return await hashlineApply(section);
} catch (e) {
if (e instanceof Error && e.message.includes('Missing hashline snapshot tag')) {
const fresh = await readForTag(path);
return await hashlineApply(section.replace(/^@@@ \S+/, `@@@ ${path}:${fresh.tag}`));
}
throw e;
} Prevention
- Always build headers by copying the exact `@@@ path:tag` line from the latest read/search result.
- Use the write tool for new files, never edit.
- Re-read after any successful edit — tags change with content.
When it happens
Trigger: Issuing a hashline edit whose header is `@@@ path` with no `:tag` suffix, using a tag format the parser did not recognize, or hand-authoring hashline sections without copying the tag from read/search output.
Common situations: Agents omitting the tag when writing edits from memory, models paraphrasing the header format, editing a file that was never read in the session, or trying to create a new file via edit instead of write.
Related errors
- Multiple hashline sections resolve to the same file
- File not found
- Found occurrences :\n\n \n\nAdd more context lines to…
- || Unable to read
- No hashline sections found in input.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/e25d2f41b6572808.
Report an issue: GitHub.
Appendix: source
Thrown at packages/coding-agent/src/edit/hashline/diff.ts:215
if (!hasBlockEdit(edits)) return edits;
const baseText = expected === undefined || liveMatches ? normalized : snapshots.byHash(absolutePath, expected)?.text;
if (baseText === undefined) {
throw createMismatchError(section, absolutePath, normalized, snapshots, expected ?? "");
}
return resolveBlockEdits(edits, baseText, section.path, nativeBlockResolver, { onUnresolved: "throw" });
}
function applyPreviewEdits(args: {
section: PatchSection;
absolutePath: string;
normalized: string;
snapshots: SnapshotStore;
options: HashlineDiffOptions;
}): ApplyResult {
const { section, absolutePath, normalized, snapshots, options } = args;
const expected = section.fileHash;
if (!options.skipHashValidation && expected === undefined) {
throw new Error(missingSnapshotTagMessage(section.path));
}
// The 4-hex tag is content-derived: when the live text hashes to it, trust
// the match and preview directly (mirrors Patcher's apply-time behavior).
const liveMatches = expected !== undefined && computeFileHash(normalized) === expected;
const edits = parsePreviewEdits(section, options.streaming);
const resolved = resolvePreviewEdits({ section, absolutePath, normalized, snapshots, expected, liveMatches, edits });
const clipboard = options.clipboard ?? {};
// Mirror the Patcher: surface clipboard sequencing mistakes with their
// targeted message before the recovery path below swallows them. Streaming
// previews stay lenient — a mid-typed op transiently violating sequencing
// must not flash an error frame.
if (!options.streaming) validateClipboardSequence(resolved, clipboard);
const applyOptions = {
clipboard,
path: absolutePath,
...(options.streaming ? { onEmptyPaste: "drop" as const } : {}),
};
if (options.skipHashValidation || expected === undefined || liveMatches) {View on GitHub (pinned to 9690622007)