santifer/career-ops · error · Error
refusing to hash non-regular file
Error message
refusing to hash non-regular file: ${childRel} What it means
hashPluginTree only knows how to hash directories (recursed) and regular files (sha256 of contents). Any other filesystem entry type — FIFOs, sockets, device files, and other specials — causes this throw. It prevents the hasher from blocking forever on a FIFO read or producing nondeterministic hashes from special files, keeping the integrity manifest well-defined.
Solutions
- Identify the entry from the message (`find <plugins-dir> ! -type f ! -type d`) and delete it if it is transient (socket/FIFO left by a process).
- Stop the process that creates the special file inside the plugin tree, or move its working directory elsewhere.
- Re-install the plugin from a clean source if the archive itself contains special files.
- Re-run hashing after the tree contains only regular files and directories.
Example fix
// before: stale socket in plugin dir aborts hashing $ ls plugins/myplugin/ server.sock index.mjs // after: remove non-regular entries, keep only real files $ rm plugins/myplugin/server.sock $ find plugins/myplugin ! -type f ! -type d -delete
Defensive patterns
Strategy: validation
Validate before calling
import { readdirSync, lstatSync } from 'fs';
import path from 'path';
function findSpecialFiles(dir, rel = '') {
const out = [];
for (const e of readdirSync(dir, { withFileTypes: true })) {
if (e.name === 'node_modules' || e.name === '.git') continue;
const childRel = rel ? `${rel}/${e.name}` : e.name;
const st = lstatSync(path.join(dir, e.name));
if (!st.isDirectory() && !st.isFile()) out.push(childRel);
else if (st.isDirectory()) out.push(...findSpecialFiles(path.join(dir, e.name), childRel));
}
return out;
}
// before calling: findSpecialFiles(pluginDir).length === 0 Try / catch
try {
hashPluginTree(pluginDir);
} catch (err) {
if (err.message.startsWith('refusing to hash non-regular file: ')) {
console.error(`Remove the FIFO/socket/device entry: ${err.message}`);
}
throw err;
} Prevention
- Keep processes (dev servers, test runners) from creating sockets/FIFOs inside the plugins directory.
- Clean transient artifacts (sockets, pipes) from plugin dirs before hashing.
- Only install plugins from archives that contain regular files and directories.
When it happens
Trigger: Calling hashPluginTree(dir) when the tree contains a named pipe, Unix socket, device node, or similar non-regular/non-directory entry created accidentally (e.g. a process wrote its socket into the plugin dir) or maliciously (a crafted plugin archive).
Common situations: A dev server left a .sock file inside the plugin folder; a test created a FIFO in the plugin directory; extracting a malicious or malformed plugin tarball containing device nodes; a docker bind-mount surfacing odd node types.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- cannot read
- refusing to hash symlink
- could not be canonicalized against the tracker workspace (…
- escapes the tracker workspace: (workspaceRoot= canonical=…
- a 40-hex commit --sha is required
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/e6ae1db4bc7c7e5e.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/_lock.mjs:52
* @param {string} dir absolute plugin directory
* @returns {{ files: Record<string,string>, integrity: string }}
*/
export function hashPluginTree(dir) {
const files = {};
const walk = (abs, rel) => {
let entries;
try { entries = readdirSync(abs, { withFileTypes: true }); }
catch (err) { throw new Error(`cannot read ${rel || '.'}: ${err.message}`); }
for (const e of entries.sort((a, b) => a.name.localeCompare(b.name))) {
if (e.name === 'node_modules' || e.name === '.git') continue;
const childAbs = path.join(abs, e.name);
const childRel = rel ? `${rel}/${e.name}` : e.name;
// lstat (not stat) so a symlink is detected, never followed.
const st = lstatSync(childAbs);
if (st.isSymbolicLink()) throw new Error(`refusing to hash symlink: ${childRel}`);
if (st.isDirectory()) walk(childAbs, childRel);
else if (st.isFile()) files[childRel] = sha256(readFileSync(childAbs));
else throw new Error(`refusing to hash non-regular file: ${childRel}`);
}
};
walk(dir, '');
// Aggregate integrity = sha256 over the deterministic sorted "rel:hash" join.
const aggregate = Object.keys(files).sort().map(k => `${k}:${files[k]}`).join('\n');
return { files, integrity: sha256(Buffer.from(aggregate)) };
}
/** Read plugins.lock (fail-open to an empty lock — like the rest of the engine). */
export function readLock(root) {
const file = lockPath(root);
if (!existsSync(file)) return { lockfileVersion: LOCK_VERSION, plugins: {} };
try {
const parsed = JSON.parse(readFileSync(file, 'utf8'));
if (!parsed || typeof parsed !== 'object' || typeof parsed.plugins !== 'object') return { lockfileVersion: LOCK_VERSION, plugins: {} };
return parsed;
} catch {
return { lockfileVersion: LOCK_VERSION, plugins: {} };View on GitHub (pinned to aac998c7ed)