{"record":{"id":"f8e3cfb3919f4ee9","repo":"ruvnet/ruflo","slug":"ruflo-x-admin-token-is-not-set-gateway-identity-writes-are","errorCode":null,"errorMessage":"RUFLO_X_ADMIN_TOKEN is not set (gateway-identity writes are admin-gated)","messagePattern":"RUFLO_X_ADMIN_TOKEN is not set \\(gateway-identity writes are admin-gated\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts","lineNumber":167,"sourceCode":"    description:\n      'Return the current owner-per-resource work-claims ledger for the open swarm (ruv://claims/board). Use when you are about to start shared work and need to know whether a resourceId is already owned. Inferring ownership from raw ClaimIssued events is wrong because releases, TTL expiry and handoffs change the answer; the board applies those rules.',\n    inputSchema: { type: 'object', properties: { ...gatewayArg } },\n    handler: async (input) => gatewayResource('ruv://claims/board', (input as Record<string, unknown>).gatewayUrl),\n  },\n  {\n    name: 'x_federation_registry',\n    description:\n      'Read the federation registry resource (ruv://federation/registry): relay URL, canonical relay tag for NIP-42, gateway pubkey, and the exact self-join steps. Use when onboarding a new node or user to the open federation. Hard-coding the relay URL is wrong because the relay verifies the NIP-42 relay tag strictly against its canonical host, which this resource states.',\n    inputSchema: { type: 'object', properties: { ...gatewayArg } },\n    handler: async (input) => gatewayResource('ruv://federation/registry', (input as Record<string, unknown>).gatewayUrl),\n  },\n  {\n    name: 'x_federation_publish',\n    description:\n      'Publish a signed coordination message to the open swarm AS THE GATEWAY identity (Status/Task/Result/…). Requires RUFLO_X_ADMIN_TOKEN. Use when a trusted operator needs a hub-level broadcast. Using this to post on behalf of an individual node is wrong because it attributes the message to the gateway, not the node — nodes should join with their own key via invite→claim and publish themselves.',\n    inputSchema: { type: 'object', properties: { ...gatewayArg, msgType: { type: 'string' }, payload: { type: 'object' } }, required: ['msgType', 'payload'] },\n    handler: async (input) => {\n      const t = adminToken(); if (!t) throw new Error('RUFLO_X_ADMIN_TOKEN is not set (gateway-identity writes are admin-gated)');\n      return gatewayTool('federation_publish', { ...(input as Record<string, unknown>), adminToken: t });\n    },\n  },\n  {\n    name: 'x_federation_invite_mint',\n    description:\n      'Mint a use-limited, expiring invite code so a new ruflo user can self-join the open federation with THEIR OWN key. Requires RUFLO_X_ADMIN_TOKEN. Use when onboarding someone. Sharing the relay owner key instead is wrong because invites are revocable, hashed at rest, and bind membership to the claimant\\'s key; the code is a bearer secret — hand it over privately.',\n    inputSchema: { type: 'object', properties: { ...gatewayArg, ttlSecs: { type: 'number', description: 'Validity (default 7 days).' }, maxUses: { type: 'number', description: 'Redemptions (default 25).' } } },\n    handler: async (input) => {\n      const t = adminToken(); if (!t) throw new Error('RUFLO_X_ADMIN_TOKEN is not set (invite minting is admin-gated)');\n      return gatewayTool('federation_invite_mint', { ...(input as Record<string, unknown>), adminToken: t });\n    },\n  },\n  {\n    name: 'x_federation_admit',\n    description:\n      'Admit a Nostr pubkey as a relay member directly (NIP-43 kind 9030). Requires RUFLO_X_ADMIN_TOKEN. Use when a known node reports its 64-hex pubkey and you want to skip the invite step. Padding or hand-editing a reported pubkey is wrong because it is a cryptographic identity; a malformed key must be re-reported, never fixed up.',\n    inputSchema: { type: 'object', properties: { ...gatewayArg, pubkey: { type: 'string', description: '64-hex secp256k1 x-only pubkey.' }, role: { type: 'string', enum: ['member', 'admin'] } }, required: ['pubkey'] },","sourceCodeStart":149,"sourceCodeEnd":185,"githubUrl":"https://github.com/ruvnet/ruflo/blob/9c61c86f06b439af2a95085ae9bb0ca839662e41/v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts#L149-L185","documentation":"Thrown by the `x_federation_publish` tool handler when `adminToken()` returns no value because the `RUFLO_X_ADMIN_TOKEN` environment variable is unset. Gateway-identity writes (publishing as the gateway identity rather than a node key) are admin-gated, so the tool refuses to run before making any network call. It is a deliberate guardrail, not an infra failure.","triggerScenarios":"Invoking `x_federation_publish` (MCP tool) in any environment where RUFLO_X_ADMIN_TOKEN is not exported: CI runners without the secret, fresh shells that never sourced the env file, MCP server processes started without inheriting the variable, or operators running unprivileged node setups where the token was intentionally withheld.","commonSituations":"Forgetting to `export RUFLO_X_ADMIN_TOKEN=...` before starting the MCP server (env must be present at process start); a .env file not loaded by the CLI; CI/CD secrets not passed to the step; attempting a hub-level broadcast from a node machine that legitimately has no admin token.","solutions":["Export RUFLO_X_ADMIN_TOKEN in the environment that launches the MCP/CLI process, then retry: `export RUFLO_X_ADMIN_TOKEN=<token>`.","If using a .env file, ensure it is actually loaded by the process and contains the key.","In CI, add the secret to the pipeline's environment for the job step.","If you are a regular node (not the gateway operator), do not use x_federation_publish — join with your own key via invite→claim and publish yourself, as the tool description advises.","Pre-flight the variable before calling: `if (!process.env.RUFLO_X_ADMIN_TOKEN) throw ...`."],"exampleFix":"// before: calling publish without the admin token in env\nawait xFederationPublish({ msgType: 'Status', payload });\n// after: check and fail fast with guidance\nif (!process.env.RUFLO_X_ADMIN_TOKEN) throw new Error('set RUFLO_X_ADMIN_TOKEN or publish with your own node key');\nawait xFederationPublish({ msgType: 'Status', payload });","handlingStrategy":"validation","validationCode":"function requireAdminToken(): string {\n  const t = process.env.RUFLO_X_ADMIN_TOKEN;\n  if (!t || t.trim() === '') throw new Error('RUFLO_X_ADMIN_TOKEN is not set; gateway-identity publish is admin-gated');\n  return t;\n}","typeGuard":"function hasAdminToken(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv & { RUFLO_X_ADMIN_TOKEN: string } {\n  return typeof env.RUFLO_X_ADMIN_TOKEN === 'string' && env.RUFLO_X_ADMIN_TOKEN.length > 0;\n}","tryCatchPattern":"try {\n  await xFederationPublish({ msgType, payload });\n} catch (e) {\n  if (e instanceof Error && e.message.includes('RUFLO_X_ADMIN_TOKEN is not set')) {\n    console.error('export RUFLO_X_ADMIN_TOKEN in the MCP server's environment, or publish with your own node key');\n  } else throw e;\n}","preventionTips":["Export RUFLO_X_ADMIN_TOKEN before launching the CLI/MCP process — env is read at process start.","In CI, declare the token as a pipeline secret and inject it into the job step.","Add a startup check that fails fast when admin-gated tools are expected to be used.","Keep operator-only tools (publish/invite_mint) on the gateway operator machine; nodes use their own keys.","Never commit tokens to the repo; load from a secrets manager or untracked .env."],"tags":["env-var","missing-token","admin-gated","configuration"],"backgroundTag":"missing-env-var","analyzedSha":"9c61c86f06b439af2a95085ae9bb0ca839662e41","analyzedAt":"2026-09-15T22:58:14.805Z","contentChangedAt":"2026-09-15T22:58:14.805Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}