can1357/oh-my-pi · error

Invalid wait regex: ${error instanceof Error ? error.message

Error message

Invalid wait regex: ${error instanceof Error ? error.message : String(error)}

What it means

DaemonBroker's wait/send operation handler compiles operation.pattern with new RegExp(pattern, "u") to match daemon log output during a wait. An invalid pattern aborts the wait before any matching is attempted, wrapping the RegExp syntax error in this message. Like the readiness check, it enforces unicode-mode validity.

Source

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

			timedOut,
			state: record.snapshot.state,
		};
	}

	async #wait(operation: Extract<DaemonOperation, { op: "wait" }>): Promise<DaemonRpcResult> {
		const record = this.#record(operation.name);
		// A wait observes exactly one launch generation. Automatic or explicit
		// relaunches reuse the managed record, so polling the record without this
		// binding can hang past an exit or consume the replacement's output.
		const boundGeneration = record.generation;
		await this.#refreshDetached(record);
		let matched: string | undefined;
		let pattern: RegExp | undefined;
		if (operation.pattern) {
			try {
				pattern = new RegExp(operation.pattern, "u");
			} catch (error) {
				throw new Error(`Invalid wait regex: ${error instanceof Error ? error.message : String(error)}`);
			}
		}
		// Readiness was actually observed: the sticky readyAt survives a fast
		// ready→exit, a live "ready" state, or a "running" daemon with no ready spec.
		const readyObserved = (): boolean =>
			record.snapshot.readyAt !== undefined ||
			record.snapshot.state === "ready" ||
			(record.snapshot.state === "running" && !record.spec.ready);
		const generationEnded = (): boolean =>
			record.generation !== boundGeneration || record.snapshot.state === "restarting";
		const condition = (): boolean => {
			if (generationEnded()) return true;
			if (pattern) {
				const match = pattern.exec(record.readinessBuffer);
				if (!match) return false;
				matched = match[0].slice(0, 500);
				return true;
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Validate the pattern client-side with `new RegExp(pattern, "u")` before issuing the wait operation.
  2. Escape user-provided fragments with a regex-escape helper before embedding them in the pattern.
  3. Omit pattern to wait purely on state/readiness instead of log matching.

Example fix

// before
await broker.wait({ name: "server", pattern: "ready (" + userPort + ")" });
// after
const safe = userPort.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = `ready \\(${safe}\\)`;
new RegExp(pattern, "u"); // validate early
await broker.wait({ name: "server", pattern });
Defensive patterns

Strategy: validation

Validate before calling

if (op.pattern !== undefined) {
  try { new RegExp(op.pattern, "u"); }
  catch (e) { throw new Error(`Bad wait pattern: ${e.message}`); }
}

Type guard

function isValidRegexU(p: unknown): p is string {
  if (typeof p !== "string") return false;
  try { new RegExp(p, "u"); return true; } catch { return false; }
}

Try / catch

try {
  await broker.wait({ name, pattern, timeoutMs });
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Invalid wait regex:")) {
    // correct or drop the pattern, then retry
  } else throw err;
}

Prevention

When it happens

Trigger: Calling a wait operation ({ op: "wait", pattern: "..." }) with a syntactically invalid regex or one illegal under the 'u' flag (dangling quantifiers, unbalanced groups, bad \u escapes).

Common situations: Patterns built dynamically by string concatenation that end up malformed; unescaped user input embedded into a pattern; JSON config backslash escaping mistakes.

Related errors


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