different-ai/openwork · error

scope must be 'workspace' or 'global'

Error message

scope must be 'workspace' or 'global'

What it means

resolveCommandsDir accepts only 'workspace' or 'global' as scope. Any other value reaches the final throw 'scope must be workspace or global'. Note the vocabulary differs from the config API, which uses 'project' — this mismatch is a common source of the error.

Source

Thrown at apps/desktop/electron/main.mjs:1506

async function writeOpencodeConfig(scope, projectDir, content) {
  const targetPath = await selectOpencodeConfigPath(resolveOpencodeConfigPath(scope, projectDir));
  await mkdir(path.dirname(targetPath), { recursive: true });
  await writeFile(targetPath, content, "utf8");
  return execResult(true, `Wrote ${targetPath}`);
}

function resolveCommandsDir(scope, projectDir) {
  if (scope === "workspace") {
    if (!String(projectDir ?? "").trim()) {
      throw new Error("projectDir is required");
    }
    return path.join(projectDir, ".opencode", "commands");
  }
  if (scope === "global") {
    return path.join(globalOpencodeRoot(), "commands");
  }
  throw new Error("scope must be 'workspace' or 'global'");
}

async function listCommandNames(scope, projectDir) {
  const commandsDir = resolveCommandsDir(scope, projectDir);
  if (!(await isDirectory(commandsDir))) {
    return [];
  }
  const entries = await readdir(commandsDir, { withFileTypes: true });
  return entries
    .filter((entry) => entry.isFile() && entry.name.endsWith(".md"))
    .map((entry) => entry.name.replace(/\.md$/, ""))
    .sort();
}

async function writeCommandFile(scope, projectDir, command) {
  const safeName = sanitizeCommandName(command?.name);
  if (!safeName) {
    throw new Error("command.name is required");

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Change scope to exactly 'workspace' or 'global' (lowercase).
  2. If migrating from the config API, rename 'project' to 'workspace' for command operations.
  3. Centralize scope constants shared by both APIs to prevent vocabulary drift.
  4. Validate scope with a union type at the call site.

Example fix

// before
await listCommandNames("project", dir);
// after
await listCommandNames("workspace", dir);
Defensive patterns

Strategy: validation

Validate before calling

const VALID_COMMAND_SCOPES = ['workspace', 'global'];
if (!VALID_COMMAND_SCOPES.includes(scope)) {
  throw new Error(`scope must be one of ${VALID_COMMAND_SCOPES.join(', ')}, got ${scope}`);
}

Type guard

function isCommandScope(scope) {
  return scope === 'workspace' || scope === 'global';
}

Prevention

When it happens

Trigger: Calling command APIs with scope='project' (the config API's term), misspelled scope, undefined/null scope, or a scope derived from settings without validation.

Common situations: Mixing up the config API ('project'|'global') with the commands API ('workspace'|'global'), typos like 'Workspace', or refactors that renamed scopes in one API but not the other.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/cff569d6770d28c3. Report an issue: GitHub.