{"record":{"id":"732f792262d13e71","repo":"ruvnet/ruflo","slug":"policy-administration-requires-an-interactive-loca","errorCode":null,"errorMessage":"policy administration requires an interactive local terminal","messagePattern":"policy administration requires an interactive local terminal","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/commands/policy.ts","lineNumber":33,"sourceCode":"  setPolicyMode,\n  upsertPolicyRule,\n  verifyPolicyLedger,\n} from '../services/policy-runtime.js';\n\nfunction argJson<T>(value: string | undefined, label: string): T {\n  if (!value) throw new Error(`${label} requires a JSON argument`);\n  try { return JSON.parse(value) as T; }\n  catch { throw new Error(`${label} must be valid JSON`); }\n}\n\nfunction print(data: unknown): CommandResult {\n  output.writeln(JSON.stringify(data, null, 2));\n  return { success: true, exitCode: 0, data };\n}\n\nfunction requireInteractiveAdministrator(): void {\n  if (!process.stdin.isTTY || !process.stdout.isTTY) {\n    throw new Error('policy administration requires an interactive local terminal');\n  }\n}\n\nexport const policyCommand: Command = {\n  name: 'policy',\n  description: 'Agentic policy engine — evaluate actions, manage rules/approvals, and verify the decision ledger (ADR-324)',\n  options: [\n    { name: 'mode', type: 'string', description: 'Policy mode: legacy | observe | enforce' },\n    { name: 'project-root', type: 'string', description: 'Project root containing .claude-flow/policy' },\n  ],\n  async action(context: CommandContext): Promise<CommandResult> {\n    const args = (context as { args?: string[] }).args ?? [];\n    const flags = (context as { flags?: Record<string, unknown> }).flags ?? {};\n    const root = String(flags.projectRoot ?? process.cwd());\n    const operation = args[0] ?? 'status';\n    try {\n      if (operation === 'init' || operation === 'migrate') {\n        if (flags.mode || args[1]) requireInteractiveAdministrator();","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/ruvnet/ruflo/blob/6b01dc5a687b26b3e218f796de45ec51f8fa9e8c/v3/@claude-flow/cli/src/commands/policy.ts#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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`.","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)."],"exampleFix":"// before (CI, no TTY)\nruflo policy init --mode enforce > deploy.log 2>&1\n// after (operator runs interactively)\nruflo policy init --mode enforce","handlingStrategy":"validation","validationCode":"function assertInteractiveAdmin(): void {\n  if (!process.stdin.isTTY || !process.stdout.isTTY) {\n    throw new Error('policy administration requires an interactive local terminal');\n  }\n}\n// call before constructing the policy command invocation:\nassertInteractiveAdmin();","typeGuard":"const isInteractiveShell = (): boolean =>\n  Boolean(process.stdin.isTTY && process.stdout.isTTY);","tryCatchPattern":"try {\n  await runPolicyMutation();\n} catch (e) {\n  const msg = e instanceof Error ? e.message : String(e);\n  if (msg.includes('interactive local terminal')) {\n    console.error('Re-run from an interactive shell (ssh -t / docker run -it).');\n    process.exit(2);\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["cli","tty","security","policy","ci"],"backgroundTag":null,"analyzedSha":"6b01dc5a687b26b3e218f796de45ec51f8fa9e8c","analyzedAt":"2026-08-12T13:20:50.148Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}