ruvnet/ruflo · error

RUFLO_X_ADMIN_TOKEN is not set (invite minting is…

Error message

RUFLO_X_ADMIN_TOKEN is not set (invite minting is admin-gated)

What it means

Thrown by the `x_federation_invite_mint` tool handler when `adminToken()` finds no `RUFLO_X_ADMIN_TOKEN` in the environment. Invite minting is admin-gated because invites are revocable, hashed at rest, and bind membership to the claimant's key, so only the relay operator may create them. The tool aborts before contacting the gateway.

Solutions

  1. Run the mint on the gateway/relay operator machine with `export RUFLO_X_ADMIN_TOKEN=<operator token>` before starting the CLI/MCP process.
  2. Verify the variable is visible to the exact process (`printenv RUFLO_X_ADMIN_TOKEN` in the same shell/container).
  3. In containers/CI, pass the token via the deployment's secret mechanism rather than relying on host shell state.
  4. If you are not the operator, ask the operator to mint the invite and hand over the `v2.<token>` code privately (it is a bearer secret).
  5. Pre-check `process.env.RUFLO_X_ADMIN_TOKEN` in scripts that orchestrate onboarding to fail with a clear message.

Example fix

// before
await xFederationInviteMint({ ttlSecs: 604800, maxUses: 25 });
// after
if (!process.env.RUFLO_X_ADMIN_TOKEN) throw new Error('invite minting requires RUFLO_X_ADMIN_TOKEN on the gateway operator machine');
await xFederationInviteMint({ ttlSecs: 604800, maxUses: 25 });
Defensive patterns

Strategy: validation

Validate before calling

if (!process.env.RUFLO_X_ADMIN_TOKEN) {
  throw new Error('RUFLO_X_ADMIN_TOKEN is not set; run invite minting on the gateway operator machine');
}

Type guard

function canMintInvites(env: NodeJS.ProcessEnv): env is NodeJS.ProcessEnv & { RUFLO_X_ADMIN_TOKEN: string } {
  return typeof env.RUFLO_X_ADMIN_TOKEN === 'string' && env.RUFLO_X_ADMIN_TOKEN.trim().length > 0;
}

Try / catch

try {
  await xFederationInviteMint({ ttlSecs: 604800, maxUses: 25 });
} catch (e) {
  if (e instanceof Error && e.message.includes('RUFLO_X_ADMIN_TOKEN is not set')) {
    console.error('invite minting is operator-only: set RUFLO_X_ADMIN_TOKEN or request an invite from the operator');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `x_federation_invite_mint` (with optional ttlSecs/maxUses) on a machine or process where RUFLO_X_ADMIN_TOKEN is unset — unprivileged node installs, MCP servers started before the token was exported, CI jobs missing the secret, or shells where the operator env file was never sourced.

Common situations: Onboarding a new federation member but running the mint command from the wrong host (a node instead of the gateway operator's machine); token exported in one terminal but the MCP server launched from another; Docker/Kubernetes containers launched without the env var; forgetting to reload env after rotating the admin token.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of ruvnet/ruflo@9c61c86f06 (2026-09-15). Data as JSON: /api/errors/789894a65ed4b460. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/mcp-tools/x-federation-tools.ts:177

    handler: async (input) => gatewayResource('ruv://federation/registry', (input as Record<string, unknown>).gatewayUrl),
  },
  {
    name: 'x_federation_publish',
    description:
      '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.',
    inputSchema: { type: 'object', properties: { ...gatewayArg, msgType: { type: 'string' }, payload: { type: 'object' } }, required: ['msgType', 'payload'] },
    handler: async (input) => {
      const t = adminToken(); if (!t) throw new Error('RUFLO_X_ADMIN_TOKEN is not set (gateway-identity writes are admin-gated)');
      return gatewayTool('federation_publish', { ...(input as Record<string, unknown>), adminToken: t });
    },
  },
  {
    name: 'x_federation_invite_mint',
    description:
      '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.',
    inputSchema: { type: 'object', properties: { ...gatewayArg, ttlSecs: { type: 'number', description: 'Validity (default 7 days).' }, maxUses: { type: 'number', description: 'Redemptions (default 25).' } } },
    handler: async (input) => {
      const t = adminToken(); if (!t) throw new Error('RUFLO_X_ADMIN_TOKEN is not set (invite minting is admin-gated)');
      return gatewayTool('federation_invite_mint', { ...(input as Record<string, unknown>), adminToken: t });
    },
  },
  {
    name: 'x_federation_admit',
    description:
      '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.',
    inputSchema: { type: 'object', properties: { ...gatewayArg, pubkey: { type: 'string', description: '64-hex secp256k1 x-only pubkey.' }, role: { type: 'string', enum: ['member', 'admin'] } }, required: ['pubkey'] },
    handler: async (input) => {
      const t = adminToken(); if (!t) throw new Error('RUFLO_X_ADMIN_TOKEN is not set (admission is admin-gated)');
      return gatewayTool('federation_admit', { ...(input as Record<string, unknown>), adminToken: t });
    },
  },
];

View on GitHub (pinned to 9c61c86f06)