{"record":{"id":"3b027b2a378e7b42","repo":"ruvnet/ruflo","slug":"node-identity-json-is-malformed","errorCode":null,"errorMessage":"node-identity.json is malformed","messagePattern":"node-identity\\.json is malformed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts","lineNumber":183,"sourceCode":"function roomLogPath(basePath: string, roomId: string): string {\n  return join(basePath, `room-${roomId}.jsonl`);\n}\n\n/**\n * Load or create this host's long-lived Ed25519 identity.\n *\n * Phase 1 minted an ephemeral key per process, which is fine for local token\n * signing but useless across hosts: a peer cannot pin a key that changes on\n * every restart. This persists one, 0600, and derives a stable nodeId from the\n * public key so identity is verifiable rather than self-asserted.\n */\nexport async function getNodeIdentity(basePath: string): Promise<NodeIdentity> {\n  ensureDir(basePath);\n  const p = identityPath(basePath);\n  if (existsSync(p)) {\n    const parsed = JSON.parse(readFileSync(p, 'utf-8')) as NodeIdentity;\n    if (!NODE_ID_RE.test(parsed.nodeId ?? '') || !HEX64_RE.test(parsed.publicKey ?? '')) {\n      throw new Error('node-identity.json is malformed');\n    }\n    return parsed;\n  }\n  const ed = await loadEd25519();\n  const priv: Uint8Array = ed.utils?.randomPrivateKey ? ed.utils.randomPrivateKey() : new Uint8Array(randomBytes(32));\n  const pub: Uint8Array = await (ed.getPublicKeyAsync ?? ed.getPublicKey)(priv);\n  const publicKey = hex(pub);\n  const identity: NodeIdentity = {\n    nodeId: createHash('sha256').update(`agentbbs:node:${publicKey}`).digest('hex').slice(0, 16),\n    publicKey,\n    privateKey: hex(priv),\n    createdAt: new Date().toISOString(),\n  };\n  writeFileSync(p, JSON.stringify(identity, null, 2) + '\\n', { mode: 0o600 });\n  try { chmodSync(p, 0o600); } catch { /* best effort on filesystems without modes */ }\n  return identity;\n}\n","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/ruvnet/ruflo/blob/2602b642d92234c710ffbe96bfb33007d481ceab/v3/@claude-flow/cli/src/mcp-tools/agentbbs-federation.ts#L165-L201","documentation":"getNodeIdentity loads (or creates) the node identity file node-identity.json under basePath. If the file exists but its nodeId is not 16 lowercase hex chars or its publicKey is not 64 hex chars, the library throws this error rather than trusting a corrupted identity. It indicates the persisted identity file is corrupt, hand-edited, or was written by an incompatible version.","triggerScenarios":"Calling getNodeIdentity(basePath) when node-identity.json exists at identityPath(basePath) but JSON-parses to an object whose parsed.nodeId fails NODE_ID_RE or whose parsed.publicKey fails HEX64_RE (including undefined/missing fields via the ?? '' fallback).","commonSituations":"Manual edits to node-identity.json; truncation/partial writes after a crash; copying an identity file from another node and editing fields by hand; older library versions writing a different format; shell-mangled or concatenated file content.","solutions":["Inspect node-identity.json and fix nodeId to exactly 16 lowercase hex chars ([0-9a-f]{16}) and publicKey to 64 lowercase hex chars","Delete the malformed node-identity.json and let getNodeIdentity regenerate a fresh keypair (note: this changes the node's identity, so peers referencing the old nodeId/publicKey must be updated)","Verify the file is valid JSON (no truncation, no concatenation of two objects) and contains only the expected fields","Restore the file from backup if the original identity must be preserved"],"exampleFix":"// before: hand-edited, malformed\n{\"nodeId\":\"my-node\",\"publicKey\":\"abc123\"}\n// after: valid identity\n{\"nodeId\":\"0a1b2c3d4e5f6071\",\"publicKey\":\"<64 lowercase hex chars>\"}","handlingStrategy":"try-catch","validationCode":"const id = JSON.parse(readFileSync(p, 'utf-8'));\nconst ok = /^[0-9a-f]{16}$/.test(id?.nodeId ?? '') && /^[0-9a-f]{64}$/.test(id?.publicKey ?? '');\nif (!ok) throw new Error('local node-identity.json is malformed; fix or delete it');","typeGuard":"function isValidNodeIdentity(v: unknown): v is NodeIdentity {\n  const o = v as NodeIdentity;\n  return !!o && typeof o.nodeId === 'string' && /^[0-9a-f]{16}$/.test(o.nodeId)\n    && typeof o.publicKey === 'string' && /^[0-9a-f]{64}$/.test(o.publicKey);\n}","tryCatchPattern":"let identity;\ntry {\n  identity = await getNodeIdentity(basePath);\n} catch (e) {\n  if (e.message === 'node-identity.json is malformed') {\n    // back up the bad file, then regenerate\n    renameSync(identityPath(basePath), identityPath(basePath) + '.bad');\n    identity = await getNodeIdentity(basePath);\n  } else throw e;\n}","preventionTips":["Never hand-edit node-identity.json; regenerate identities via the API","Write the identity file atomically (temp file + rename) to avoid truncation on crash","Back up the file before upgrades or migrations","Validate with the regexes after any manual copy between nodes"],"tags":["validation","identity","crypto","file"],"backgroundTag":"schema-validation-failed","analyzedSha":"2602b642d92234c710ffbe96bfb33007d481ceab","analyzedAt":"2026-09-15T22:58:14.805Z","contentChangedAt":"2026-09-15T22:58:14.805Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}