block/buzz · warning · std::io::Error
oauth cache is not a regular file
Error message
oauth cache is not a regular file
What it means
buzz-agent reads its OAuth token cache with hardening: it opens the cache path with O_NOFOLLOW (symlinks are rejected at the kernel level) and then fstats the open fd; if the entry is not a regular file (FIFO, device node, directory), it returns this InvalidData error. This is a deliberate fail-closed security check — a cache that exists but is not a plain file is treated as tampering. Callers (read_cache) convert the error to None and fall back to a fresh browser-based auth flow.
Source
Thrown at crates/buzz-agent/src/auth.rs:1803
///
/// On Unix `O_NOFOLLOW` rejects a symlinked cache path at the kernel level
/// (no stat/open TOCTOU), and `fchmod` on the already-open handle repairs a
/// loose mode against the pinned inode rather than re-resolving the path.
/// A cache that exists but cannot be secured is an error, so the caller fails
/// closed instead of using an exposed file.
#[cfg(unix)]
fn read_private_cache(path: &Path) -> io::Result<Vec<u8>> {
use std::io::Read;
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
let mut file = fs::OpenOptions::new()
.read(true)
.custom_flags(nix::libc::O_NOFOLLOW)
.open(path)?;
let meta = file.metadata()?;
if !meta.file_type().is_file() {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"oauth cache is not a regular file",
));
}
// Tighten in place on the open fd if any group/other bit is set. fchmod
// targets the inode we already hold, so no attacker can swap the path
// between the check and the repair.
if meta.permissions().mode() & 0o077 != 0 {
file.set_permissions(fs::Permissions::from_mode(0o600))?;
}
let mut body = Vec::new();
file.read_to_end(&mut body)?;
Ok(body)
}
/// Non-Unix: token persistence and reading are both disabled until a
/// Windows-specific owner-only DACL is implemented. Any legacy token fileView on GitHub (pinned to dad5a33865)
Solutions
- Inspect the path: `file ~/.config/buzz-agent/oauth/*/<hash>.json` — expect 'JSON text data', not 'fifo' or 'directory'.
- Remove the offending non-regular entry: `rm` (fifo/file) or `rmdir`, then re-run the agent to trigger a fresh interactive login that rewrites a proper 0600 cache file.
- If you set a cache_dir_override, make sure it points at a directory only this agent manages.
- If you did not create the entry yourself, treat it as a security incident on that home directory — the check exists precisely to catch planted files.
Example fix
# before file ~/.config/buzz-agent/oauth/default/<hash>.json # -> FIFO # after rm ~/.config/buzz-agent/oauth/default/<hash>.json buzz-agent login # recreates a regular, 0600 cache file
Defensive patterns
Strategy: fallback
Validate before calling
// before reading, confirm the cache is a regular file (symlinks fail separately)
use std::os::unix::fs::FileTypeExt;
fn cache_is_plain_file(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p)
.ok()
.map(|m| m.file_type().is_file())
.unwrap_or(false)
} Type guard
fn is_regular_file(p: &std::path::Path) -> bool {
std::fs::symlink_metadata(p)
.map(|m| m.file_type().is_file())
.unwrap_or(false)
} Try / catch
// read_cache already fails closed: map any read error to None and fall back
// to interactive re-auth instead of using an untrusted file
fn read_cache(path: &Path) -> Option<CachedToken> {
read_private_cache(path).ok()?
.pipe(|body| serde_json::from_slice(&body).ok())
} Prevention
- Never replace the cache file with symlinks/FIFOs in scripts; only the agent should write it.
- Give each agent user its own HOME/cache_dir_override; do not share cache directories across users.
- If auth unexpectedly re-prompts every run, `file` the cache path — this error silently downgrades to re-login.
- Treat any unexplained non-regular file at the path as tampering and investigate, not delete-and-forget.
When it happens
Trigger: read_private_cache at crates/buzz-agent/src/auth.rs:501-507 hits this when something replaced $HOME/.config/buzz-agent/oauth/<namespace>/<sha256>.json (or the cache_dir_override path) with a named pipe (`mkfifo`), a device file, or a directory. Note a symlink surfaces earlier as an ELOOP open error, not this message; this error is specifically 'opened fine but fstat says not a regular file'.
Common situations: A test or debugging session left a FIFO at the cache path; the cache directory is shared and another tool wrote a directory named like the cache file; tampering/compromised home dir. User-visible symptom is benign: the agent ignores the cache and forces a new OAuth login each run.
Related errors
- oauth cache path has no parent directory
- git pack cache path must be available
- BUZZ_UDS_PATH {uds_path} exists but is not a socket
- Failed to bind UDS {uds_path}: {e}
- failed to install rustls crypto provider
AI-assisted analysis of block/buzz@dad5a33865 (2026-08-20).
Data as JSON: /api/errors/cd8a98f008c7478b.
Report an issue: GitHub.