ruvnet/ruflo · error · Error
policy administration requires an interactive local terminal
Error message
policy administration requires an interactive local terminal
What it means
Thrown by requireInteractiveAdministrator() when either process.stdin.isTTY or process.stdout.isTTY is false. Mutating policy operations (init with a mode, rule add, budget set, revoke) require a human-in-the-loop terminal so approval and audit context is visible; non-interactive contexts (piped input, CI logs, redirected stdout, nohup) are refused to prevent blind automated policy changes.
Source
Thrown at v3/@claude-flow/cli/src/commands/policy.ts:33
setPolicyMode,
upsertPolicyRule,
verifyPolicyLedger,
} from '../services/policy-runtime.js';
function argJson<T>(value: string | undefined, label: string): T {
if (!value) throw new Error(`${label} requires a JSON argument`);
try { return JSON.parse(value) as T; }
catch { throw new Error(`${label} must be valid JSON`); }
}
function print(data: unknown): CommandResult {
output.writeln(JSON.stringify(data, null, 2));
return { success: true, exitCode: 0, data };
}
function requireInteractiveAdministrator(): void {
if (!process.stdin.isTTY || !process.stdout.isTTY) {
throw new Error('policy administration requires an interactive local terminal');
}
}
export const policyCommand: Command = {
name: 'policy',
description: 'Agentic policy engine — evaluate actions, manage rules/approvals, and verify the decision ledger (ADR-324)',
options: [
{ name: 'mode', type: 'string', description: 'Policy mode: legacy | observe | enforce' },
{ name: 'project-root', type: 'string', description: 'Project root containing .claude-flow/policy' },
],
async action(context: CommandContext): Promise<CommandResult> {
const args = (context as { args?: string[] }).args ?? [];
const flags = (context as { flags?: Record<string, unknown> }).flags ?? {};
const root = String(flags.projectRoot ?? process.cwd());
const operation = args[0] ?? 'status';
try {
if (operation === 'init' || operation === 'migrate') {
if (flags.mode || args[1]) requireInteractiveAdministrator();View on GitHub (pinned to 6b01dc5a68)
Solutions
- Run the mutating command from a real interactive shell (sit at the terminal, do not pipe or redirect).
- For SSH, allocate a TTY: `ssh -t host ruflo policy init --mode enforce`.
- For Docker, run with `-it`: `docker run -it ... ruflo policy init --mode enforce`.
- For CI, perform policy mutation in a pre-deploy step executed by an operator, not the pipeline itself; the pipeline may run `policy status` (read-only, no TTY needed).
Example fix
// before (CI, no TTY) ruflo policy init --mode enforce > deploy.log 2>&1 // after (operator runs interactively) ruflo policy init --mode enforce
Defensive patterns
Strategy: validation
Validate before calling
function assertInteractiveAdmin(): void {
if (!process.stdin.isTTY || !process.stdout.isTTY) {
throw new Error('policy administration requires an interactive local terminal');
}
}
// call before constructing the policy command invocation:
assertInteractiveAdmin(); Type guard
const isInteractiveShell = (): boolean => Boolean(process.stdin.isTTY && process.stdout.isTTY);
Try / catch
try {
await runPolicyMutation();
} catch (e) {
const msg = e instanceof Error ? e.message : String(e);
if (msg.includes('interactive local terminal')) {
console.error('Re-run from an interactive shell (ssh -t / docker run -it).');
process.exit(2);
}
throw e;
} Prevention
- Run mutating policy commands from a real terminal, never from CI/cron.
- Use `ssh -t` and `docker run -it` for remote/container sessions.
- Keep policy mutation out of automated deploy pipelines.
When it happens
Trigger: Running `ruflo policy init --mode enforce`, `rule add`, `budget set`, or `revoke` inside a CI runner, cron job, ssh -t-less session, with stdin piped from a file, with stdout redirected to a log, or under any process supervisor that detaches the TTY.
Common situations: CI/CD pipelines that try to initialize policy mode during deploy, Docker containers run non-interactively, scripts that pipe a here-doc into the CLI, or running over `ssh host ruflo ...` without `-t`.
Related errors
- approval issuance requires an authenticated human identity a
- ${label} requires a JSON argument
- ${label} must be valid JSON
- mode must be legacy, observe, or enforce
- revoke requires an approval id
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/732f792262d13e71.
Report an issue: GitHub.