can1357/oh-my-pi · error

${message} || Unable to read ${sectionPath}

Error message

${message} || Unable to read ${sectionPath}

What it means

readSectionText() wraps readEditFileText() for hashline section reads and rethrows any failure as a single Error whose message is the original error message, or a generic 'Unable to read <sectionPath>' fallback if the underlying error carried no message. The sectionPath here is the hashline header's file path (may include the snapshot tag), so most real failures propagate the underlying message (missing file, generated-file refusal) verbatim.

Source

Thrown at packages/coding-agent/src/edit/hashline/diff.ts:70

	 * authoring input; the final apply path still validates through Patcher.
	 */
	skipHashValidation?: boolean;
	/**
	 * Clipboard register shared across the sections of one patch preview.
	 * `CUT` in an earlier section feeds a register-backed `PUT` in a later one,
	 * so the preview matches apply. Multi-section previews MUST thread one
	 * register through sections in patch order; omitted, each section gets a
	 * private register (same-file cut/put still previews correctly).
	 */
	clipboard?: Clipboard;
}

async function readSectionText(absolutePath: string, sectionPath: string): Promise<string> {
	try {
		return await readEditFileText(absolutePath, sectionPath);
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		throw new Error(message || `Unable to read ${sectionPath}`);
	}
}

/**
 * Streaming previews recompute on every streamed chunk; re-reading the target
 * file from disk each tick dominates the cost on large files. Cache the raw
 * section text keyed by mtime+size so any on-disk change invalidates
 * naturally. Used by the streaming path only — the args-complete pass always
 * reads fresh.
 */
const streamingTextCache = new Map<string, { mtimeMs: number; size: number; rawContent: string }>();
const STREAMING_TEXT_CACHE_MAX = 8;

async function readSectionTextCached(absolutePath: string, sectionPath: string): Promise<string> {
	let stamp: { mtimeMs: number; size: number } | undefined;
	try {
		const stat = await Bun.file(absolutePath).stat();
		stamp = { mtimeMs: stat.mtimeMs, size: stat.size };

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-read the target with the read tool to get the current correct path and snapshot tag, then re-issue the edit.
  2. Verify the file exists on disk at the path in the header (ls / stat).
  3. Check the error message prefix above the '|| Unable to read' fallback for the real cause (ENOENT vs permissions vs generated-file refusal).

Example fix

// before
hashlineEdit({ section: `@@@ src/oldName.ts:tag\n- old line` });
// after
// after rename, re-read to get fresh path+tag:
hashlineEdit({ section: `@@@ src/newName.ts:a1b2\n- old line` });
Defensive patterns

Strategy: try-catch

Validate before calling

const stat = await Bun.file(sectionPath.replace(/:[0-9a-f]+$/, '')).exists();
if (!stat) throw new Error(`Section path does not exist: ${sectionPath}`);

Try / catch

try {
  const raw = await readSectionText(absPath, sectionPath);
  return applyEdits(raw);
} catch (e) {
  const msg = e instanceof Error ? e.message : String(e);
  if (msg.includes('Unable to read') || msg.includes('File not found')) {
    return reReadAndReauthorEdit(sectionPath);
  }
  throw e;
}

Prevention

When it happens

Trigger: Applying a hashline edit whose section header references a file that does not exist, is unreadable (permissions), was deleted between read and edit, or whose path is malformed — any readEditFileText failure inside the edit-apply path.

Common situations: Stale absolute/relative path after a rename, typos in the section header path, editing a file already deleted in the working tree, or an LLM hallucinating a path not present in its read output.

Related errors


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