ruvnet/ruflo · error

--token-stdin: JSON is missing required field "access_token"

Error message

--token-stdin: JSON is missing required field "access_token"

What it means

Thrown by ruflo's token-stdin login path after stdin was successfully read and JSON.parse succeeded, but the resulting object has no truthy top-level `access_token` string. The CLI expects exactly one JSON object of the shape {"access_token", "refresh_token"?, "expires_in", "scope"}. It is a shape check on externally supplied credentials, not a network or auth-server failure.

Solutions

  1. Pipe a single flat JSON object with a non-empty top-level access_token: echo '{"access_token":"eyJ...","expires_in":3600}' | ruflo auth login --token-stdin
  2. If your source nests the token, unwrap it first: jq '{access_token: .data.access_token, expires_in: .expires_in}' | ruflo auth login --token-stdin
  3. Verify the payload before sending: cat token.json | jq -e '.access_token | type == "string" and length > 0'
  4. If you only hold a refresh token (no access token), use the interactive `ruflo auth login` flow instead of --token-stdin

Example fix

// before
echo '{"refresh_token":"rt_123"}' | ruflo auth login --token-stdin
// after
echo '{"access_token":"eyJhbGci...","refresh_token":"rt_123","expires_in":3600,"scope":"openid"}' | ruflo auth login --token-stdin
Defensive patterns

Strategy: validation

Validate before calling

const raw = await readStdin();
let parsed: unknown;
try { parsed = JSON.parse(raw); } catch { fail('stdin is not valid JSON'); }
if (typeof (parsed as any)?.access_token !== 'string' || (parsed as any).access_token.length === 0) {
  fail('payload needs a non-empty top-level "access_token" string');
}
await login({ tokens: parsed as OAuthTokenResponse, method: 'token-stdin' });

Type guard

function isTokenStdinPayload(v: unknown): v is { access_token: string; refresh_token?: string; expires_in?: number; scope?: string } {
  return typeof v === 'object' && v !== null &&
    typeof (v as Record<string, unknown>).access_token === 'string' &&
    (v as Record<string, unknown>).access_token!.length > 0;
}

Try / catch

try {
  await rufloLoginFromStdin();
} catch (e) {
  if (e instanceof Error && e.message.includes('missing required field "access_token"')) {
    console.error('Expected: {"access_token":"...","refresh_token"?,"expires_in","scope"}');
    process.exit(2);
  }
  throw e;
}

Prevention

When it happens

Trigger: Running `ruflo auth login --token-stdin` and piping a JSON object that lacks `access_token`: e.g. {"refresh_token":"..."} only, a wrapper like {"data":{"access_token":...}}, {"token":"..."} with the wrong key name, or access_token:"" (empty string is falsy and rejected). Any valid JSON without a non-empty top-level access_token triggers it.

Common situations: CI pipelines piping tokens from a vault/IdP whose JSON envelope nests the token one level deep; scripts written against a different CLI's token format; copy-pasting an ID-token payload with a different field name; generating the payload with jq using the wrong key.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/833abddbad01133e. Report an issue: GitHub.

Appendix: source

Thrown at v3/@claude-flow/cli/src/auth/client.ts:191

 * `{access_token, refresh_token?, expires_in, scope}`. Wire format is not
 * specified by ADR-306 — defined here as typed JSON rather than a bare
 * token string, so scope/expiry are explicit rather than inferred.
 */
export async function tokenStdinLogin(input: NodeJS.ReadableStream = process.stdin): Promise<LoginResult> {
  const chunks: Buffer[] = [];
  for await (const chunk of input) chunks.push(chunk as Buffer);
  const raw = Buffer.concat(chunks).toString('utf-8').trim();
  if (!raw) throw new Error('--token-stdin: no input received on stdin');

  let parsed: { access_token?: string; refresh_token?: string; expires_in?: number; scope?: string };
  try {
    parsed = JSON.parse(raw);
  } catch {
    throw new Error(
      '--token-stdin expects a single JSON object: {"access_token","refresh_token"?,"expires_in","scope"}',
    );
  }
  if (!parsed.access_token) throw new Error('--token-stdin: JSON is missing required field "access_token"');

  const tokens: OAuthTokenResponse = {
    access_token: parsed.access_token,
    token_type: 'Bearer',
    refresh_token: parsed.refresh_token,
    expires_in: parsed.expires_in,
  };
  return { tokens, method: 'token-stdin' };
}

/**
 * Refreshes an access token. Classifies failure into network-unreachable
 * vs. a reachable-but-erroring server so callers can print an honest
 * message instead of collapsing both into "offline" (ADR-308 failure
 * policy: local ruflo functionality is never affected by auth being
 * unavailable, but the diagnostic should say WHY it's unavailable).
 */
export async function refreshAccessToken(refreshTokenValue: string): Promise<OAuthTokenResponse> {

View on GitHub (pinned to fa13ee4ad6)