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
- Change scope to exactly 'workspace' or 'global' (lowercase).
- If migrating from the config API, rename 'project' to 'workspace' for command operations.
- Centralize scope constants shared by both APIs to prevent vocabulary drift.
- 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
- Remember commands use 'workspace', config uses 'project' — centralize both in constants.
- Validate scope from persisted settings before passing it to IPC.
- Add union types so mismatches are caught at build time.
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
- scope must be 'project' or 'global'
- name is required
- t("providers.provider_id_required")
- invalid_command_template
- command.template is required
AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01).
Data as JSON: /api/errors/cff569d6770d28c3.
Report an issue: GitHub.