can1357/oh-my-pi · critical

Daemon broker token is empty

Error message

Daemon broker token is empty

What it means

After startup the broker reads the auth token file from the runtime directory and trims it. The token file exists but is empty, so the broker refuses to start — it requires a non-empty shared secret to authenticate broker connections.

Source

Thrown at packages/coding-agent/src/launch/broker.ts:1416

	const lease = await acquireBrokerLease(runtimeDir);
	if (!lease) return;
	setProcessName("omp daemon broker");
	// Record the scope's project dir so `omp ps` can map this hash-keyed runtime
	// dir back to its project (and derive the Windows pipe name) offline.
	void writeDaemonScopeMeta(runtimeDir, projectDir).catch(error => {
		logger.warn("Failed to record daemon scope metadata", {
			error: error instanceof Error ? error.message : String(error),
		});
	});
	// Reclaim sibling daemon scopes left behind by dead brokers (issue #8674).
	// Detached and non-throwing so it never delays clients connecting to us.
	void pruneDeadDaemonRuntimeDirs(runtimeDir).catch(error => {
		logger.warn("Daemon runtime prune failed", {
			error: error instanceof Error ? error.message : String(error),
		});
	});
	const token = (await Bun.file(path.join(runtimeDir, TOKEN_FILE)).text()).trim();
	if (!token) throw new Error("Daemon broker token is empty");
	const broker = new DaemonBroker(projectDir, runtimeDir, token, idleGraceMs, restartBackoffBaseMs);
	const cancelCleanup = postmortem.register("daemon-broker", () => broker.shutdown());
	try {
		await broker.run();
	} finally {
		cancelCleanup();
		await releaseBrokerLease(lease);
	}
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Delete the runtime directory (or just the token file) so the client's readOrCreateToken regenerates a fresh token, then restart
  2. Verify no concurrent broker is writing the same runtime dir; ensure one broker per project dir
  3. Check disk full / permission issues that could truncate the token file

Example fix

// before
const token = (await Bun.file(path.join(runtimeDir, TOKEN_FILE)).text()).trim();
if (!token) throw new Error("Daemon broker token is empty");
// after — regenerate instead of failing hard
let token = (await Bun.file(path.join(runtimeDir, TOKEN_FILE)).text()).trim();
if (!token) { token = await createToken(runtimeDir); }
Defensive patterns

Strategy: retry

Validate before calling

const tokenPath = path.join(runtimeDir, 'token');
const stat = await Bun.file(tokenPath).exists().then(ok => ok ? Bun.file(tokenPath) : null);
if (stat && (await stat.text()).trim().length === 0) await fs.rm(tokenPath); // force regeneration

Try / catch

try {
  await startDaemonBrokerFromEnvironment();
} catch (err) {
  if (err.message.includes('token is empty')) {
    await fs.rm(path.join(runtimeDir, 'token'), { force: true });
    await startDaemonBrokerFromEnvironment(); // regenerate token
  } else throw err;
}

Prevention

When it happens

Trigger: The token file at <runtimeDir>/<TOKEN_FILE> was truncated by a crash, created empty by a partial initialization, or wiped by cleanup/disk pressure while the worker was starting.

Common situations: Concurrent brokers racing on the same runtime dir; kill -9 during token generation; stale runtime dirs reused after a reboot; pruneDeadDaemonRuntimeDirs removing files from a live dir.

Related errors


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