{"record":{"id":"befddcca36eda7af","repo":"ruvnet/ruflo","slug":"hextobytes-odd-length-hex-string","errorCode":null,"errorMessage":"hexToBytes: odd-length hex string","messagePattern":"hexToBytes: odd-length hex string","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"plugins/ruflo-neural-trader/src/signed-artifact.ts","lineNumber":171,"sourceCode":"\n/**\n * Canonical bytes for signing = `JSON.stringify(body)` UTF-8 encoded.\n *\n * Matches the plugin-registry signer (publish-registry.ts:127-151) and the\n * CWE-347 smoke (smoke-plugin-registry-signature.mjs:193-200): plain\n * `JSON.stringify` with NO whitespace and NO key sort. The body shape is\n * authored by us (the signer), so deterministic key order is guaranteed by\n * construction — no canonicalizer needed.\n */\nfunction canonicalBytes(body: SignedBacktestArtifactBody): Uint8Array {\n  const message = JSON.stringify(body);\n  return new TextEncoder().encode(message);\n}\n\nfunction hexToBytes(hex: string): Uint8Array {\n  const clean = hex.replace(/^0x/, '');\n  if (clean.length % 2 !== 0) {\n    throw new Error(`hexToBytes: odd-length hex string`);\n  }\n  const out = new Uint8Array(clean.length / 2);\n  for (let i = 0; i < out.length; i++) {\n    out[i] = parseInt(clean.slice(i * 2, i * 2 + 2), 16);\n  }\n  return out;\n}\n\nfunction bytesToHex(bytes: Uint8Array): string {\n  let s = '';\n  for (let i = 0; i < bytes.length; i++) {\n    s += bytes[i].toString(16).padStart(2, '0');\n  }\n  return s;\n}\n","sourceCodeStart":153,"sourceCodeEnd":187,"githubUrl":"https://github.com/ruvnet/ruflo/blob/fa13ee4ad60ac2090b1480656eb233521790d640/plugins/ruflo-neural-trader/src/signed-artifact.ts#L153-L187","documentation":"handleSaveConfig() wraps its entire file operation block — backup creation, merge read, and the final fs.writeFile — in one try/catch and rethrows any underlying error prefixed with 'Failed to save configuration:'. So this message is a wrapper: the real cause (permissions, missing directory, disk full, target is a directory) is in the appended error.message and in the preceding console.error('Failed to save config:') log line on the server. Note the safePath was already validated by validateConfigPath(), so this is an I/O failure, not a path-policy rejection.","triggerScenarios":"Target file is read-only or owned by another user (EACCES/EPERM); safePath points at an existing directory (EISDIR); the parent directory does not exist (ENOENT) — validateConfigPath never mkdirs; disk full (ENOSPC); on Windows the file is locked by another process; merge:true with a corrupt existing JSON file logs a warning but the write itself can still fail for the reasons above.","commonSituations":"Running the MCP server as a different user than the one owning the config file; containers with read-only mounted config dirs; CI environments with small tmpfs; config file accidentally created as a directory by an earlier bad script.","solutions":["Check the server console output first — console.error('Failed to save config:', error) prints the raw cause (EACCES, EISDIR, ENOENT, ...) that the wrapped message only summarizes","Fix permissions on the target: chmod u+w claude-flow.config.json (or chown to the server user)","Create the parent directory before saving: mkdir -p config/ — the tool does not create intermediate directories","Verify the target is a regular file, not a directory: ls -la claude-flow.config.json; remove and recreate if it is a directory","Free disk space or enlarge the volume if the appended message mentions ENOSPC"],"exampleFix":"// before: server run from a dir where ./config/ does not exist\nawait client.callTool('config_save', { path: 'config/claude-flow.config.json', config: cfg });\n// -> Failed to save configuration: ENOENT: no such file or directory\n\n// after: ensure parent dir exists, then save\nimport { mkdir } from 'fs/promises';\nawait mkdir(path.dirname(targetPath), { recursive: true });\nawait client.callTool('config_save', { path: 'config/claude-flow.config.json', config: cfg });","handlingStrategy":"try-catch","validationCode":"import { stat, mkdir, access, constants } from 'fs/promises';\nimport { dirname } from 'path';\n\nasync function ensureConfigWritable(target: string): Promise<void> {\n  await mkdir(dirname(target), { recursive: true });\n  try {\n    const s = await stat(target);\n    if (!s.isFile()) throw new Error(`${target} is a directory`);\n    await access(target, constants.W_OK);\n  } catch (e: any) {\n    if (e.code !== 'ENOENT') throw e; // missing file is fine, we will create it\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await client.callTool('config_save', { path, config, createBackup: true });\n} catch (e) {\n  const msg = e instanceof Error ? e.message : '';\n  if (/EACCES|EPERM/.test(msg)) throw new Error(`No write permission for ${path} — fix file ownership`);\n  if (/EISDIR/.test(msg)) throw new Error(`${path} is a directory — remove it`);\n  if (/ENOSPC/.test(msg)) throw new Error('Disk full — free space before saving config');\n  throw e;\n}","preventionTips":["Pre-flight ensureConfigWritable() before the first save in a session","Watch the server console — console.error('Failed to save config:') carries the raw errno","Persist configs in a writable directory you own; avoid read-only mounts for live config"],"tags":["mcp","config","filesystem","io"],"backgroundTag":"config-write-failed","analyzedSha":"fa13ee4ad60ac2090b1480656eb233521790d640","analyzedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}