can1357/oh-my-pi · error · Error

secret placeholder key at ${keyPath} exists but is empty or

Error message

secret placeholder key at ${keyPath} exists but is empty or unreadable

What it means

Secret placeholder keys are stored in a key file created with an exclusive 'wx' flag so only one process generates the key. If the file exists but readPlaceholderKeyFile cannot obtain non-empty valid content (another process is mid-write, or the file is corrupted/unreadable), the code refuses to cache or derive a key.

Source

Thrown at packages/coding-agent/src/secrets/index.ts:48

		cachedPlaceholderKeys.set(keyPath, existing);
		return existing;
	}

	const generated = crypto.randomBytes(32).toString("base64url");
	await fs.promises.mkdir(path.dirname(keyPath), { recursive: true });
	try {
		await fs.promises.writeFile(keyPath, generated, { flag: "wx", mode: 0o600 });
		cachedPlaceholderKeys.set(keyPath, generated);
		return generated;
	} catch (err) {
		if ((err as NodeJS.ErrnoException).code !== "EEXIST") throw err;
		// Another process won the create race but may still be mid-write: `wx`
		// creates the file empty before the bytes land. Wait for non-empty content
		// instead of caching an empty key (which would be a known, dictionaryable
		// key and would not match tokens other processes persist with the real key).
		const winner = await readPlaceholderKeyFile(keyPath, true);
		if (winner === undefined) {
			throw new Error(`secret placeholder key at ${keyPath} exists but is empty or unreadable`);
		}
		cachedPlaceholderKeys.set(keyPath, winner);
		return winner;
	}
}

/** Return an existing placeholder key for redaction without creating a new key file. */
export async function getExistingSecretPlaceholderKey(keyDir?: string): Promise<string | undefined> {
	const keyPath = keyDir ? path.join(keyDir, "secret-placeholder.key") : getSecretPlaceholderKeyPath();
	const cached = cachedPlaceholderKeys.get(keyPath);
	if (cached !== undefined) return cached;
	// Redaction-only: this key is loaded solely to redact an existing key file from
	// provider-visible tool output, never to mint placeholders. A truncated/corrupt
	// or unreadable key must NOT block startup for replace-only/no-secret sessions —
	// an invalid key is not a usable HMAC anyway, and a tool reading the same file
	// gets the same bytes, so there is nothing sensitive to redact.
	let existing: string | undefined;
	try {

View on GitHub (pinned to 9690622007)

Solutions

  1. Delete the empty/corrupt key file so a healthy process can recreate it
  2. Wait and retry — the winning process may still be mid-write
  3. Check filesystem permissions on the secrets directory

Example fix

// before
// keyPath exists but is 0 bytes
throw new Error(`secret placeholder key at ${keyPath} exists but is empty or unreadable`);
// after
await fs.rm(keyPath); // remove the empty/corrupt file, then retry the operation
Defensive patterns

Strategy: retry

Validate before calling

const stat = await fs.stat(keyPath).catch(() => null);
if (stat && stat.size === 0) throw new Error("placeholder key file is empty; delete it and retry");

Type guard

null

Try / catch

try {
  key = await getSecretPlaceholderKey();
} catch (err) {
  if (err instanceof Error && err.message.includes("empty or unreadable")) {
    await Bun.sleep(50); // creator may still be mid-write
    key = await getSecretPlaceholderKey();
  } else throw err;
}

Prevention

When it happens

Trigger: Concurrent processes racing to create the placeholder key file; the key file exists with zero bytes or cannot be read/decoded.

Common situations: Crash between file creation and write leaving an empty file; permission problems on the secrets directory; heavy multi-process startup (multiple omp instances) hitting the create race.

Related errors


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