{"record":{"id":"a555a8e6ab8e8734","repo":"astrid-runtime/astrid","slug":"inspect-legacy-revocation-file-error","errorCode":null,"errorMessage":"inspect legacy revocation file: {error}","messagePattern":"inspect legacy revocation file: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/astrid-gateway/src/revocations.rs","lineNumber":70,"sourceCode":"    Ok(home.etc_dir().join(\"gateway-revocations.json\"))\n}\n\n/// Whether the released JSON index exists. Used only to fail closed when a\n/// standalone gateway has no authoritative KV wiring during startup.\npub fn legacy_file_exists() -> anyhow::Result<bool> {\n    let path = revocations_path()?;\n    match std::fs::symlink_metadata(&path) {\n        Ok(metadata) => {\n            if metadata.file_type().is_symlink() || !metadata.is_file() {\n                anyhow::bail!(\n                    \"legacy gateway revocation path is not a regular file: {}\",\n                    path.display()\n                );\n            }\n            Ok(true)\n        },\n        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(false),\n        Err(error) => Err(anyhow::anyhow!(\"inspect legacy revocation file: {error}\")),\n    }\n}\n\n/// Hard cap on the legacy migration file. Each entry is ~50 bytes of JSON;\n/// `10 MiB` gives migration ample room without permitting an unbounded boot\n/// allocation from a corrupted or hostile operator file.\nconst MAX_REVOCATIONS_FILE_BYTES: u64 = 10 * 1024 * 1024;\n\nfn read_legacy_bytes(path: &std::path::Path) -> anyhow::Result<Vec<u8>> {\n    #[cfg(unix)]\n    let file = {\n        use std::os::unix::fs::OpenOptionsExt as _;\n        std::fs::OpenOptions::new()\n            .read(true)\n            .custom_flags(nix::libc::O_NOFOLLOW | nix::libc::O_CLOEXEC)\n            .open(path)\n            .with_context(|| format!(\"open legacy revocation file {}\", path.display()))?\n    };","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-gateway/src/revocations.rs#L52-L88","documentation":"legacy_file_exists stats the legacy revocation JSON file to decide whether a one-time migration is needed. NotFound maps to Ok(false) (no legacy file — normal), but any other I/O error opening/inspecting the file (permission denied, is-a-directory, etc.) is wrapped in this error. It means the legacy file exists but could not be inspected.","triggerScenarios":"std::fs metadata/open of home.etc_dir()/gateway-revocations.json fails with an error kind other than NotFound — e.g. EACCES (permission denied), EISDIR (path is a directory), or ELOOP.","commonSituations":"gateway-revocations.json accidentally replaced by a directory; service user lacking read permission on etc/; filesystem errors (broken symlink loop, NFS issues) on the host.","solutions":["Check permissions on $ASTRID_HOME/etc and the gateway-revocations.json path so the service user can read it","Verify the path is a regular file, not a directory or broken symlink (ls -la)","If the legacy file is no longer needed, remove/rename it so the stat resolves to NotFound","Inspect the wrapped std::io::Error for the exact OS errno"],"exampleFix":"# before\nls -la $ASTRID_HOME/etc/gateway-revocations.json  # permission denied / is a directory\n# after\nsudo chown astrid:astrid $ASTRID_HOME/etc/gateway-revocations.json\n# or remove the stray directory and restore the file / delete it if migration is done","handlingStrategy":"try-catch","validationCode":"fn legacy_path_sane(home: &std::path::Path) -> bool {\n    let p = home.join(\"etc\").join(\"gateway-revocations.json\");\n    match std::fs::metadata(&p) {\n        Ok(m) => m.is_file(),\n        Err(e) => e.kind() == std::io::ErrorKind::NotFound,\n    }\n}","typeGuard":null,"tryCatchPattern":"match revocations::legacy_file_exists() {\n    Ok(exists) => exists,\n    Err(e) if e.to_string().contains(\"inspect legacy revocation file\") => {\n        log::warn!(\"legacy revocation file unreadable, skipping migration: {e}\");\n        false // or fail closed per startup policy\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Ensure the service user owns/read access to $ASTRID_HOME/etc","Never replace the JSON file with a directory or symlink loop","Clean up the legacy file once migration is complete"],"tags":["filesystem","io","permissions"],"backgroundTag":"file-read-failed","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}