{"record":{"id":"83026084df4a55bd","repo":"vercel/turborepo","slug":"lockfile-should-be-in-repo","errorCode":null,"errorMessage":"lockfile should be in repo","messagePattern":"lockfile should be in repo","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/turborepo-repository/src/change_mapper/mod.rs","lineNumber":335,"sourceCode":"    }\n\n    fn get_changed_packages_from_lockfile(\n        &self,\n        lockfile_content: &[u8],\n    ) -> Result<Vec<ExternalDependencyChange>, ChangeMapError> {\n        self.pkg_graph\n            .changed_packages_from_lockfile_contents(lockfile_content)\n            .map_err(Into::into)\n    }\n\n    pub fn lockfile_changed(\n        turbo_root: &AbsoluteSystemPath,\n        changed_files: &HashSet<AnchoredSystemPathBuf>,\n        lockfile_path: &AbsoluteSystemPath,\n    ) -> bool {\n        let lockfile_path_relative = turbo_root\n            .anchor(lockfile_path)\n            .expect(\"lockfile should be in repo\");\n\n        changed_files.iter().any(|f| f == &lockfile_path_relative)\n    }\n}\n\n#[derive(thiserror::Error, Debug)]\npub enum ChangeMapError {\n    #[error(transparent)]\n    Wax(#[from] wax::BuildError),\n    #[error(transparent)]\n    ChangedPackages(#[from] ChangedPackagesError),\n}\n\n#[cfg(test)]\nmod test {\n    use std::collections::HashSet;\n\n    use test_case::test_case;","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/vercel/turborepo/blob/f9245100cf0d31d96628804ead485f6bf226e55a/crates/turborepo-repository/src/change_mapper/mod.rs#L317-L353","documentation":"ChangeMapper::lockfile_changed (change_mapper/mod.rs:335) anchors the detected lockfile path against the repo root with turbo_root.anchor(lockfile_path) and `.expect(\"lockfile should be in repo\")` panics when the lockfile is not underneath the root turbo inferred. The invariant assumes package-manager discovery only ever finds the lockfile inside the workspace; when a layout breaks that (nested checkouts, root inference picking the wrong directory), this panic aborts the run.","triggerScenarios":"`turbo run` during change detection (watch mode / affected) where the lockfile detected via packageManager inference sits above the directory turbo treats as root — e.g. a partial/nested checkout of a monorepo, or root inference landing on a subdir while pnpm-workspace.yaml/lockfile live at the parent.","commonSituations":"Sparse or nested checkouts of a monorepo Running turbo from a subdirectory in layouts where the lockfile is one level above the inferred root Unusual TURBO_ROOT / discovery configuration after repo restructuring","solutions":["Run turbo from the actual repository root so the lockfile lands inside the inferred root","Verify the inferred root (inspect `turbo run <task> --dry-run=json` root/workspace data) and fix the layout or config that misdirects it","Ensure the lockfile and workspace config (pnpm-workspace.yaml etc.) are inside the checked-out tree, not above it","If the layout is legitimate, report it — the expect should become a real error message"],"exampleFix":"# before: run from nested checkout, lockfile one level up\ncd repo/apps/web && turbo run build   # panics: lockfile should be in repo\n# after: run from the root that contains the lockfile\ncd repo && turbo run build --filter=web","handlingStrategy":"validation","validationCode":"// before change detection, assert the invariant the panic encodes\nlet rel = turbo_root.anchor(lockfile_path);\nif rel.is_err() {\n    anyhow::bail!(\"lockfile {lockfile_path} is outside turbo root {turbo_root}; run from the repo root\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always invoke turbo from the root that contains the lockfile","Keep workspace config and lockfile inside the checked-out tree","Add a CI sanity step asserting the lockfile is within the detected root"],"tags":["lockfile","workspace","discovery","panic","affected"],"backgroundTag":"lockfile-outside-root","analyzedSha":"f9245100cf0d31d96628804ead485f6bf226e55a","analyzedAt":"2026-08-17T10:46:15.696Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}