ruvnet/ruflo · error · Error

policy administration requires an interactive local terminal

Error message

policy administration requires an interactive local terminal

What it means

requireInteractiveAdministrator() throws when stdin or stdout is not a TTY and the requested policy operation mutates state (init/migrate with a --mode or positional mode, rule add, budget set, revoke). Policy administration is deliberately gated to a local interactive terminal so CI jobs, piped stdin, and daemons cannot silently rewrite policy rules or budgets.

Solutions

  1. Run the mutating command in a real interactive terminal (local shell or interactive ssh)
  2. For scripted environments, mutate the policy state via the policy-runtime library API instead of the CLI
  3. Note plain `ruflo policy init` without --mode does NOT require a TTY — only mode changes and rule/budget/revoke mutations do
  4. As a last resort wrap in a pty (script -qec 'ruflo policy init --mode enforce' /dev/null), but prefer the API route

Example fix

# before (CI, non-TTY)
ruflo policy init --mode enforce

# after (plain migrate is allowed non-interactively)
ruflo policy init   # no --mode; set the mode later from an interactive terminal
Defensive patterns

Strategy: validation

Validate before calling

const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
const needsTty = ['init-with-mode', 'rule add', 'budget set', 'revoke'];
if (needsTty.includes(op) && !isInteractive) {
  // fall back to the policy-runtime library API instead of the CLI
}

Type guard

function isInteractiveTerminal(): boolean {
  return process.stdin.isTTY === true && process.stdout.isTTY === true;
}

Try / catch

try {
  await runPolicyCli(['init', '--mode', mode]);
} catch (err) {
  if (err instanceof Error && err.message.includes('interactive local terminal')) {
    // surface to the user to run manually, or use the library API from a TTY session
  } else throw err;
}

Prevention

When it happens

Trigger: Running `ruflo policy init --mode enforce` in CI, under nohup, from a Docker exec without -t, or piping input: echo ... | ruflo policy rule add '{...}'. Also triggered when only one of stdin/stdout is redirected.

Common situations: Trying to automate policy setup in Dockerfiles or CI pipelines; running through ssh with redirected stdio; testing via a shell script with stdin from a file.

Related errors


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

Appendix: 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 fa13ee4ad6)