multica-ai/multica · error · Error

Invalid desktop runtime config: ${field} must be a valid URL

Error message

Invalid desktop runtime config: ${field} must be a valid URL

What it means

Returned by hydrateCodexSkills when os.RemoveAll of <codexHome>/skills fails. Codex is the only runtime needing two-stage hydration because the daemon sets CODEX_HOME to a per-task directory; the skills dir must be cleared first so removed user skills and stale workspace skills from a previous run do not remain visible to the codex CLI.

Source

Thrown at apps/desktop/src/shared/runtime-config.ts:147

    throw new Error(`Invalid desktop runtime config: ${field} must be a non-empty string`);
  }
  return value;
}

function optionalString(value: unknown, field: string): string | undefined {
  if (value === undefined) return undefined;
  if (typeof value !== "string" || value.trim().length === 0) {
    throw new Error(`Invalid desktop runtime config: ${field} must be a non-empty string when set`);
  }
  return value;
}

function normalizeHttpUrl(value: string, field: string): string {
  let url: URL;
  try {
    url = new URL(value.trim());
  } catch {
    throw new Error(`Invalid desktop runtime config: ${field} must be a valid URL`);
  }
  if (url.protocol !== "http:" && url.protocol !== "https:") {
    throw new Error(`Invalid desktop runtime config: ${field} must use http or https`);
  }
  url.search = "";
  url.hash = "";
  return trimTrailingSlash(url.toString());
}

function normalizeWsUrl(value: string, field: string): string {
  let url: URL;
  try {
    url = new URL(value.trim());
  } catch {
    throw new Error(`Invalid desktop runtime config: ${field} must be a valid URL`);
  }
  if (url.protocol !== "ws:" && url.protocol !== "wss:") {
    throw new Error(`Invalid desktop runtime config: ${field} must use ws or wss`);

View on GitHub (pinned to 2c0912b6ec)

Solutions

  1. Check the wrapped *PathError for the exact path, then inspect its ownership and permissions; chown/chmod it to the daemon user.
  2. If the env-root is recycled between runs, ensure the same daemon user owns it, or clear stale per-task dirs before re-preparing.
  3. Remount read-only filesystems read-write, or move the env-root to writable storage.
  4. Remove undeletable held-open files (lsof on the path) or reboot stale NFS handles, then retry.

Example fix

// before: env-root owned by previous daemon user → RemoveAll fails
# ls -ld /var/lib/app/envs/task-123/codex-home/skills
drwx------ 1 olduser olduser ...

// after: hand the tree back to the daemon user
# chown -R daemonuser:daemongroup /var/lib/app/envs/task-123
Defensive patterns

Strategy: validation

Validate before calling

skillsDir := filepath.Join(codexHome, "skills")
if fi, err := os.Lstat(skillsDir); err == nil && !fi.IsDir() {
    return fmt.Errorf("codex skills path is not a directory: %s", skillsDir)
}
if err := checkWritable(codexHome); err != nil {
    return fmt.Errorf("codex home not writable: %w", err)
}

Try / catch

if err := hydrateCodexSkills(codexHome, ws, disabled, logger); err != nil {
    if strings.Contains(err.Error(), "clear codex skills dir") {
        // ownership/perms problem on the per-task CODEX_HOME: chown or wipe env-root, retry prepare
    }
}

Prevention

When it happens

Trigger: hydrateCodexSkills(codexHome, ...) is called and os.RemoveAll(filepath.Join(codexHome, "skills")) errors — the per-task CODEX_HOME (or a file inside its skills dir) cannot be removed: permission mismatch, read-only mount, an undeletable file held open, or a path that is not a directory tree the daemon user owns.

Common situations: Per-task codex home created by a different user (daemon restarted under a new UID); skills dir on a read-only or NFS mount with stale handles; a file under skills/ with no write permission on its parent; SELinux/AppArmor denying unlink.

Related errors


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/b5ad1c7dd4c41473. Report an issue: GitHub.