{"record":{"id":"cff569d6770d28c3","repo":"different-ai/openwork","slug":"scope-must-be-workspace-or-global","errorCode":null,"errorMessage":"scope must be 'workspace' or 'global'","messagePattern":"scope must be 'workspace' or 'global'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/main.mjs","lineNumber":1506,"sourceCode":"\nasync function writeOpencodeConfig(scope, projectDir, content) {\n  const targetPath = await selectOpencodeConfigPath(resolveOpencodeConfigPath(scope, projectDir));\n  await mkdir(path.dirname(targetPath), { recursive: true });\n  await writeFile(targetPath, content, \"utf8\");\n  return execResult(true, `Wrote ${targetPath}`);\n}\n\nfunction resolveCommandsDir(scope, projectDir) {\n  if (scope === \"workspace\") {\n    if (!String(projectDir ?? \"\").trim()) {\n      throw new Error(\"projectDir is required\");\n    }\n    return path.join(projectDir, \".opencode\", \"commands\");\n  }\n  if (scope === \"global\") {\n    return path.join(globalOpencodeRoot(), \"commands\");\n  }\n  throw new Error(\"scope must be 'workspace' or 'global'\");\n}\n\nasync function listCommandNames(scope, projectDir) {\n  const commandsDir = resolveCommandsDir(scope, projectDir);\n  if (!(await isDirectory(commandsDir))) {\n    return [];\n  }\n  const entries = await readdir(commandsDir, { withFileTypes: true });\n  return entries\n    .filter((entry) => entry.isFile() && entry.name.endsWith(\".md\"))\n    .map((entry) => entry.name.replace(/\\.md$/, \"\"))\n    .sort();\n}\n\nasync function writeCommandFile(scope, projectDir, command) {\n  const safeName = sanitizeCommandName(command?.name);\n  if (!safeName) {\n    throw new Error(\"command.name is required\");","sourceCodeStart":1488,"sourceCodeEnd":1524,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/desktop/electron/main.mjs#L1488-L1524","documentation":"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.","triggerScenarios":"Calling command APIs with scope='project' (the config API's term), misspelled scope, undefined/null scope, or a scope derived from settings without validation.","commonSituations":"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.","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."],"exampleFix":"// before\nawait listCommandNames(\"project\", dir);\n// after\nawait listCommandNames(\"workspace\", dir);","handlingStrategy":"validation","validationCode":"const VALID_COMMAND_SCOPES = ['workspace', 'global'];\nif (!VALID_COMMAND_SCOPES.includes(scope)) {\n  throw new Error(`scope must be one of ${VALID_COMMAND_SCOPES.join(', ')}, got ${scope}`);\n}","typeGuard":"function isCommandScope(scope) {\n  return scope === 'workspace' || scope === 'global';\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","arguments","enum-value","commands"],"backgroundTag":"invalid-argument-value","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}