{"record":{"id":"d773e15a3eb83a5e","repo":"Hmbown/CodeWhale","slug":"external-credential-consent-path-for-must-be-ab","errorCode":null,"errorMessage":"external credential consent path for {} must be absolute","messagePattern":"external credential consent path for (.+?) must be absolute","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/config/src/external_credentials.rs","lineNumber":396,"sourceCode":"                self.consent_version\n            );\n        }\n        if self.provider != provider.as_str() {\n            bail!(\n                \"external credential consent is scoped to provider {:?}, not {}\",\n                self.provider,\n                provider.as_str()\n            );\n        }\n        if self.source != source {\n            bail!(\n                \"external credential consent source mismatch for {} (expected {})\",\n                provider.as_str(),\n                source.as_str()\n            );\n        }\n        if !self.path.is_absolute() {\n            bail!(\n                \"external credential consent path for {} must be absolute\",\n                provider.as_str()\n            );\n        }\n        let normalized = resolve_external_credential_path(&self.path)?;\n        if normalized != self.path {\n            bail!(\n                \"external credential consent path for {} must be lexically normalized: {}\",\n                provider.as_str(),\n                quote_os_path(&self.path)\n            );\n        }\n        if self.path != resolved_path {\n            bail!(\n                \"external credential path changed for {}; consent covers {}, current path is {}\",\n                provider.as_str(),\n                quote_os_path(&self.path),\n                quote_os_path(resolved_path)","sourceCodeStart":378,"sourceCodeEnd":414,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/config/src/external_credentials.rs#L378-L414","documentation":"Thrown by `ExternalCredentialConsentToml::validate_read_scope` when the consent record's stored `path` is not an absolute filesystem path. Consent is bound to a specific absolute credential file path; relative paths would make the grant ambiguous depending on the process working directory.","triggerScenarios":"A consent record was created (or hand-written into the TOML) with a relative path like `~/.config/creds` (unexpanded) or `creds/token.json` instead of an absolute path.","commonSituations":"User hand-edited the consent file with `~` unexpanded or a relative path; a tool that wrote the consent used a non-canonicalized path; migrating config between machines with different layouts.","solutions":["Fix the consent record's `path` to the fully expanded absolute path (no `~`, no relative segments).","Revoke the consent and re-consent so the path is captured as absolute.","Expand `~` / env vars before writing any consent record programmatically."],"exampleFix":"// before (consent TOML)\npath = \"~/.codewhale/creds/google.json\"\n// after\npath = \"/home/alice/.codewhale/creds/google.json\"","handlingStrategy":"validation","validationCode":"fn is_absolute_path(p: &std::path::Path) -> bool {\n    p.is_absolute()\n}\n// call before writing/accepting a consent record:\n// assert!(is_absolute_path(&consent.path));","typeGuard":null,"tryCatchPattern":"match store.read_grant(provider, source, &path) {\n    Err(e) if e.to_string().contains(\"must be absolute\") => {\n        // rewrite the consent record with the expanded absolute path, then retry\n        let abs = expand_tilde_and_env(&consent.path);\n        fix_consent_path(provider, abs)?;\n    }\n    other => other,\n}","preventionTips":["Expand `~` and environment variables before persisting any credential path.","Use `std::path::Path::is_absolute` as a pre-write assertion on consent records.","Never hand-write consent TOML entries with relative paths."],"tags":["config","credentials","consent","path"],"backgroundTag":"invalid-config-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}