ruvnet/ruflo · error · Error
hexToBytes: odd-length hex string
Error message
hexToBytes: odd-length hex string
What it means
session resume loads <sessionId>.json from .claude-flow/sessions/ under process.cwd() inside a single try block that covers file read AND JSON.parse; any failure in that block is collapsed into 'Session not found: <id>'. So the message can mean the file is genuinely missing, but also that it is unreadable or corrupt — and since sessions live on disk keyed by the server's working directory, resuming from a different cwd guarantees the miss. Checksum mismatches do NOT throw; they only append to an errors array.
Source
Thrown at plugins/ruflo-neural-trader/src/signed-attribution.ts:386
name: item.name,
score: item.score,
rank: i + 1,
}));
}
/* ---------------------------------------------------------------------- */
/* Helpers */
/* ---------------------------------------------------------------------- */
function canonicalBytes(body: SignedAttributionArtifactBody): Uint8Array {
const message = JSON.stringify(body);
return new TextEncoder().encode(message);
}
function hexToBytes(hex: string): Uint8Array {
const clean = hex.replace(/^0x/, '');
if (clean.length % 2 !== 0) {
throw new Error('hexToBytes: odd-length hex string');
}
const out = new Uint8Array(clean.length / 2);
for (let i = 0; i < out.length; i++) {
out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);
}
return out;
}
function bytesToHex(bytes: Uint8Array): string {
let s = '';
for (let i = 0; i < bytes.length; i++) {
s += bytes[i].toString(16).padStart(2, '0');
}
return s;
}
/**
* Mulberry32 — small, fast, deterministic PRNG. Same algorithm everywhereView on GitHub (pinned to fa13ee4ad6)
Solutions
- Verify the file exists before resuming: ls .claude-flow/sessions/<sessionId>.json from the server's working directory
- Run the MCP server from the same directory the session was created in — the session store is rooted at process.cwd()
- List available sessions first (session files in .claude-flow/sessions/) and confirm the exact ID including case
- If the file exists but is corrupt (0 bytes / cut off), delete it and start a fresh session — the error message hides JSON.parse failures, so test with: node -e "JSON.parse(require('fs').readFileSync(process.argv[1],'utf8'))" .claude-flow/sessions/<id>.json
- Persist .claude-flow/sessions across container restarts via a mounted volume
Example fix
// before
await client.callTool('session_resume', { sessionId }); // throws [1127] when run from another cwd
// after: pre-check existence relative to the server's cwd
import { existsSync } from 'fs';
import { join } from 'path';
const file = join(process.cwd(), '.claude-flow/sessions', `${sessionId}.json`);
if (!existsSync(file)) throw new Error(`No session file at ${file} — start the MCP server in the session's original directory`);
await client.callTool('session_resume', { sessionId }); Defensive patterns
Strategy: validation
Validate before calling
import { existsSync, readFileSync } from 'fs';
import { join } from 'path';
function sessionFileReadable(sessionId: string, cwd = process.cwd()): boolean {
const file = join(cwd, '.claude-flow/sessions', `${sessionId}.json`);
if (!existsSync(file)) return false;
try { JSON.parse(readFileSync(file, 'utf-8')); return true; } // catches the corrupt-JSON case
catch { return false; }
} Try / catch
try {
await client.callTool('session_resume', { sessionId });
} catch (e) {
if (e instanceof Error && e.message.startsWith('Session not found')) {
// message also covers unreadable/corrupt files — check the file directly to distinguish
if (!sessionFileReadable(sessionId)) throw new Error(`session ${sessionId} missing or corrupt under cwd`);
}
throw e;
} Prevention
- Always run the MCP server from the same directory that created the session
- Mount .claude-flow/sessions as a volume in containers so sessions survive restarts
- Save immediately after important state changes instead of relying on a later resume
When it happens
Trigger: session_resume with an ID that was never saved or was saved in another process run from a different directory; the .claude-flow/sessions/<id>.json file was deleted or never flushed (crash before save); the JSON file is truncated/corrupt so JSON.parse throws and gets swallowed into the same message; sessionId casing mismatch on case-sensitive filesystems.
Common situations: Starting the MCP server from a different directory between runs (sessions are cwd-relative, not global); cleanup scripts wiping .claude-flow/; assuming sessions persist across container restarts when the dir was never mounted.
Related errors
- signAttributionArtifact: privateKey must be 32 bytes (got ${
- localSingleEntryPageRank: sourceIndex ${src} out of range [0
- SSRF guard: only HTTPS URLs are permitted, got ${parsed.prot
- SSRF guard: private/loopback host rejected — ${host}
- SSRF guard: invalid URL — ${rawUrl}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/46d1a97d7647eea2.
Report an issue: GitHub.