{"record":{"id":"77b87b70ed42a12b","repo":"vercel/turborepo","slug":"pnpm-workspace-parse-error","errorCode":"pnpm-workspace_parse_error","errorMessage":"failed to parse ${workspaceFile}","messagePattern":"failed to parse (.+?)","errorType":"exception","errorClass":"ConvertError","httpStatus":null,"severity":"error","filePath":"packages/turbo-workspaces/src/utils.ts","lineNumber":373,"sourceCode":"  workspaceFileName\n}: {\n  workspaceRoot: string;\n  workspaceFileName: string;\n}): Array<string> {\n  const workspaceFile = path.join(workspaceRoot, workspaceFileName);\n  if (existsSync(workspaceFile)) {\n    try {\n      const workspaceConfig = yaml.load(readFileSync(workspaceFile, \"utf8\"));\n      // validate it's the type we expect\n      if (\n        workspaceConfig instanceof Object &&\n        \"packages\" in workspaceConfig &&\n        Array.isArray(workspaceConfig.packages)\n      ) {\n        return workspaceConfig.packages as Array<string>;\n      }\n    } catch (err) {\n      throw new ConvertError(`failed to parse ${workspaceFile}`, {\n        type: \"pnpm-workspace_parse_error\"\n      });\n    }\n  }\n\n  return [];\n}\n\nfunction expandPaths({\n  root,\n  lockFile,\n  workspaceConfig\n}: {\n  root: string;\n  lockFile: string;\n  workspaceConfig?: string;\n}) {\n  const fromRoot = (p: string) => path.join(root, p);","sourceCodeStart":355,"sourceCodeEnd":391,"githubUrl":"https://github.com/vercel/turborepo/blob/9f94a7d215b3881942527dd526afa3fc650869d5/packages/turbo-workspaces/src/utils.ts#L355-L391","documentation":"Thrown by getYamlWorkspaces() in turbo-workspaces when pnpm-workspace.yaml (or aube-workspace.yaml, which shares the code path) exists but yaml.load throws - i.e. the file is syntactically invalid YAML. ConvertError type \"pnpm-workspace_parse_error\" with the full file path in the message. Subtlety: it is thrown only on YAML syntax errors - a file that parses but has the wrong shape (packages not an array, or missing) silently yields [] and no error. Reached from pnpm/aube read(); detect() passes as soon as the file exists, so the failure appears after detection, mid-read of an otherwise recognized project.","triggerScenarios":"pnpm-workspace.yaml with tabs used for indentation; an unquoted asterisk scalar such as `packages: apps/*` (YAML reads `*` as an alias reference and js-yaml throws 'unknown alias'); duplicate keys; merge-conflict markers left after a git merge; malformed indent after hand-editing the catalog/workspace list.","commonSituations":"Copying a workspace glob from docs into the wrong YAML shape (`packages: apps/*` instead of a list under packages); tabs inserted by a configured-for-Tabs editor; merge conflicts in frequently-edited pnpm-workspace.yaml (catalog entries) resolved with markers still present; renames from pnpm-workspace.yml or partial writes after a crashed save.","solutions":["Get the precise line from a YAML parser: `node -e \"require('js-yaml').load(require('fs').readFileSync('pnpm-workspace.yaml','utf8'))\"` - js-yaml prints position info that the ConvertError deliberately omits","Fix the syntax: use spaces (never tabs) for indentation, keep `packages:` as a YAML list of quoted globs, remove conflict markers and duplicate keys","Verify the shape is `packages:\\n  - \\\"apps/*\\\"` (array of strings) - remember a non-array packages value will not throw but silently disables workspace discovery","Validate the file in CI (parse it with js-yaml and assert Array.isArray(cfg.packages)) before running migrations"],"exampleFix":"# before - pnpm-workspace.yaml that throws (alias star, tab indent)\npackages: apps/*\n\tpackages:\n\t\t- apps/*\n\n# after - valid YAML, quoted globs, space indent\npackages:\n  - \"apps/*\"\n  - \"packages/*\"","handlingStrategy":"validation","validationCode":"import { readFileSync, existsSync } from \"node:fs\";\nimport path from \"node:path\";\nimport yaml from \"js-yaml\";\n\nconst wsPath = path.join(root, \"pnpm-workspace.yaml\");\nif (existsSync(wsPath)) {\n  const cfg = yaml.load(readFileSync(wsPath, \"utf8\")) as { packages?: unknown };\n  if (!Array.isArray(cfg?.packages)) {\n    throw new Error(`${wsPath}: 'packages' must be an array of globs`);\n  }\n}\nconst details = await getWorkspaceDetails({ root });","typeGuard":"import { ConvertError } from \"turbo-workspaces\";\n\nfunction isPnpmWorkspaceParseError(err: unknown): err is ConvertError {\n  return err instanceof ConvertError && err.type === \"pnpm-workspace_parse_error\";\n}","tryCatchPattern":"try {\n  const details = await getWorkspaceDetails({ root });\n} catch (err) {\n  if (err instanceof ConvertError && err.type === \"pnpm-workspace_parse_error\") {\n    try {\n      yaml.load(readFileSync(path.join(root, \"pnpm-workspace.yaml\"), \"utf8\"));\n    } catch (e) {\n      console.error(`Fix pnpm-workspace.yaml: ${(e as Error).message}`); // includes line numbers\n    }\n    process.exit(1);\n  } else throw err;\n}","preventionTips":["Use spaces (never tabs) in pnpm-workspace.yaml","Write globs as quoted list items under packages: (\"apps/*\"), never as a bare `packages: apps/*` scalar","Add a CI check that js-yaml-parses pnpm-workspace.yaml and asserts Array.isArray(packages)","Resolve git merge conflicts fully - conflict markers are valid-looking but fatal YAML"],"tags":["pnpm","yaml","workspace-config","parse-error","migration"],"backgroundTag":"yaml-parse-error","analyzedSha":"9f94a7d215b3881942527dd526afa3fc650869d5","analyzedAt":"2026-08-16T19:47:29.531Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}