can1357/oh-my-pi · error · Error

Invalid global daemon service name

Error message

Invalid global daemon service name: ${JSON.stringify(service)}

What it means

getGlobalDaemonRuntimeDir(service) builds a machine-global runtime directory path by joining the service name under the daemon runtime root. Because the name becomes a filesystem path segment, it is validated against /^[a-z0-9][a-z0-9._-]*$/i; any name containing path separators, leading dots, spaces, or other special characters throws this error to prevent path traversal or malformed runtime paths.

Solutions

  1. Sanitize the service name before calling: strip or replace illegal characters, e.g. name.replace(/[^a-zA-Z0-9._-]/g, "-").
  2. Ensure the name starts with an alphanumeric character (prepend one or trim leading dots/dashes).
  3. Derive the service name from a fixed, known identifier (package/tool name) rather than free-form user input.
  4. Validate with the same regex the library uses ( /^[a-z0-9][a-z0-9._-]*$/i ) at your config-loading boundary and reject invalid config early.

Example fix

// before
const dir = getGlobalDaemonRuntimeDir(`${org}/${tool}`); // "acme/preview" throws

// after
const service = `${org}-${tool}`.replace(/[^a-zA-Z0-9._-]/g, "-").replace(/^[^a-zA-Z0-9]+/, "");
const dir = getGlobalDaemonRuntimeDir(service);
Defensive patterns

Strategy: validation

Validate before calling

const SERVICE_RE = /^[a-z0-9][a-z0-9._-]*$/i;
function isValidDaemonService(name: string): boolean {
  return SERVICE_RE.test(name);
}
if (!isValidDaemonService(service)) throw new Error(`Refusing invalid daemon service name: ${service}`);

Type guard

function isValidDaemonService(name: string): boolean {
  return /^[a-z0-9][a-z0-9._-]*$/i.test(name);
}

Try / catch

let dir: string;
try {
  dir = getGlobalDaemonRuntimeDir(service);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("Invalid global daemon service name")) {
    dir = getGlobalDaemonRuntimeDir(sanitizeServiceName(service));
  } else throw err;
}

Prevention

When it happens

Trigger: Calling getGlobalDaemonRuntimeDir() with a service string that is empty, starts with a dot or non-alphanumeric character, or contains characters outside [a-z0-9._-] — e.g. "my daemon", "svc/v2", "../escape", "-svc", "".

Common situations: Passing a user-supplied profile/project name straight through as the daemon service name; building the service id by concatenating strings with slashes or colons (e.g. "org:tool"); interpolating an env value or config key that contains spaces or dots at the start.

Related errors


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

Appendix: source

Thrown at packages/utils/src/dirs.ts:971

export function getDaemonRuntimeRoot(): string {
	return dirs.rootSubdir(path.join("run", "daemons"), "state");
}

/** Get the daemon runtime directory for a project (~/.omp/run/daemons/<hash>; XDG default: $XDG_STATE_HOME/omp/run/daemons/<hash>). */
export function getDaemonRuntimeDir(projectDir: string): string {
	const key = Bun.hash.wyhash(path.resolve(projectDir)).toString(16).padStart(16, "0");
	return path.join(getDaemonRuntimeRoot(), key);
}

/** Root directory containing every machine-global daemon service scope. */
export function getGlobalDaemonRuntimeRoot(): string {
	return path.join(getBaseConfigRoot(), "run", "daemons", "global");
}

/** Get a profile-independent runtime directory for a machine-global daemon service. */
export function getGlobalDaemonRuntimeDir(service: string): string {
	if (!/^[a-z0-9][a-z0-9._-]*$/i.test(service)) {
		throw new Error(`Invalid global daemon service name: ${JSON.stringify(service)}`);
	}
	return path.join(getGlobalDaemonRuntimeRoot(), service);
}

/** Get the provider in-flight root directory (~/.omp/run/provider-inflight; XDG default: $XDG_STATE_HOME/omp/run/provider-inflight). */
export function getProviderInFlightRoot(): string {
	return dirs.rootSubdir(path.join("run", "provider-inflight"), "state");
}

/** Get the marketplaces registry path (~/.omp/marketplaces.json; XDG default: $XDG_DATA_HOME/omp/marketplaces.json). Adopts a legacy registry on first XDG resolution. */
export function getMarketplacesRegistryPath(): string {
	const registryPath = dirs.rootSubdir("marketplaces.json", "data");
	adoptLegacyFile(path.join(dirs.configRoot, "marketplaces.json"), registryPath);
	return registryPath;
}

// =============================================================================
// Project subdirectories (.omp/*)

View on GitHub (pinned to 9690622007)