ruvnet/ruflo · error
--token-stdin expects a single JSON object
Error message
--token-stdin expects a single JSON object: {"access_token","refresh_token"?,"expires_in","scope"} What it means
tokenStdinLogin got non-empty stdin but JSON.parse failed: the input is not a single valid JSON object. The expected wire format (defined in client.ts, not by ADR-306) is one object {"access_token": string, "refresh_token"?: string, "expires_in": number, "scope": string} — not a bare token string, not a JWT, not shell-quoted JSON, not multiple JSON documents.
Solutions
- Wrap the token exactly as specified: printf '{"access_token":"%s","expires_in":3600,"scope":"..."}' "$TOK" | ruflo auth login --token-stdin
- Strip non-JSON noise: pipe through a jq stage — jq -n --arg t "$TOK" '{access_token:$t,expires_in:3600,scope:"api"}' — so the bytes are guaranteed valid JSON
- Validate locally first: the same bytes must pass jq . or node -e 'JSON.parse(require("fs").readFileSync(0))'
- Remember the required field: even valid JSON without access_token throws the sibling 'missing required field' error — include it
Example fix
# before — bare token string (not JSON)
echo "$ACCESS_TOKEN" | ruflo auth login --token-stdin
# after — single JSON object on stdin
printf '{"access_token":"%s","expires_in":3600,"scope":"api"}' "$ACCESS_TOKEN" \
| ruflo auth login --token-stdin Defensive patterns
Strategy: validation
Validate before calling
// validate shape before the CLI sees it
const obj = JSON.parse(raw);
if (typeof obj?.access_token !== 'string' || typeof obj?.expires_in !== 'number')
throw new Error('token JSON malformed — rebuild the envelope');
// or generate it: jq -n --arg t "$TOK" '{access_token:$t,expires_in:3600,scope:"api"}' Type guard
function isTokenEnvelope(v: unknown): v is { access_token: string; refresh_token?: string; expires_in?: number; scope?: string } {
return typeof v === 'object' && v !== null &&
typeof (v as any).access_token === 'string';
} Try / catch
try { await tokenStdinLogin(input); }
catch (e) {
if (e instanceof Error && e.message.includes('expects a single JSON object')) {
// input wasn't JSON: pipe through `jq .` or rebuild the envelope and retry
}
throw e;
} Prevention
- Pipe the token through jq so output is guaranteed-valid JSON
- Use the documented envelope; a bare JWT string is rejected
- Keep log noise out of the token stream — separate transport from logging
When it happens
Trigger: `ruflo auth login --token-stdin` fed a raw access-token string or JWT (both invalid JSON), JSON wrapped in single quotes by a shell heredoc, pretty-printed JSON split across documents, or output with log lines mixed around the JSON (e.g. 'Fetching... {"access_token"...}').
Common situations: Piping `echo $ACCESS_TOKEN` instead of a JSON envelope; credential helpers emitting extra prose; YAML/ini-style config pasted instead of JSON; trailing commas or comments (invalid in JSON); UTF-8 BOM from Windows tooling.
Related errors
- --token-stdin: no input received on stdin
- --token-stdin: JSON is missing required field "access_token"
- authorization was denied or failed
- Cognitum auth service returned an unexpected response
- Cognitum refresh response did not contain an access token
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/65fe7a7078ea5430.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/auth/client.ts:187
}
/**
* `--token-stdin`: reads one JSON object from stdin,
* `{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 failureView on GitHub (pinned to fa13ee4ad6)