{"record":{"id":"499f76b5b8c318ac","repo":"Yeachan-Heo/oh-my-codex","slug":"remove-requires-an-interactive-terminal-rerun-wit","errorCode":null,"errorMessage":"remove requires an interactive terminal; rerun with --force in non-interactive environments","messagePattern":"remove requires an interactive terminal; rerun with --force in non-interactive environments","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/cli/agents.ts","lineNumber":208,"sourceCode":"    if (existsSync(path)) return path;\n  }\n  throw new Error(`agent not found: ${normalized}`);\n}\n\nasync function confirmRemove(path: string): Promise<boolean> {\n  const rl = createInterface({ input: process.stdin, output: process.stdout });\n  try {\n    const answer = (await rl.question(`Delete native agent ${path}? [y/N]: `)).trim().toLowerCase();\n    return answer === 'y' || answer === 'yes';\n  } finally {\n    rl.close();\n  }\n}\n\nfunction ensureInteractiveRemove(force: boolean): void {\n  if (force) return;\n  if (process.stdin.isTTY && process.stdout.isTTY) return;\n  throw new Error('remove requires an interactive terminal; rerun with --force in non-interactive environments');\n}\n\nasync function editNativeAgent(\n  name: string,\n  options: { cwd?: string; scope?: AgentScope; editor?: string } = {},\n): Promise<string> {\n  const path = resolveExistingAgentPath(name, options);\n  const editor = options.editor ?? process.env.EDITOR ?? process.env.VISUAL ?? 'vi';\n  const result = spawnSync(editor, [path], {\n    stdio: 'inherit',\n    shell: true,\n    env: process.env,\n  });\n  if (result.status !== 0) {\n    throw new Error(`editor exited with status ${result.status ?? 'unknown'}`);\n  }\n  return path;\n}","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/Yeachan-Heo/oh-my-codex/blob/3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2/src/cli/agents.ts#L190-L226","documentation":"agent remove prompts for confirmation, which requires both stdin and stdout to be TTYs. In non-interactive environments (CI, pipes, scripts) without --force, this error is thrown instead of hanging on a prompt nobody can answer.","triggerScenarios":"Running `omx agent remove x` in CI, via ssh -c, inside a pipe (`echo | omx agent remove x`), or from any non-TTY context.","commonSituations":"CI cleanup steps, docker containers without TTY, automation scripts, cron jobs.","solutions":["Add --force to skip confirmation in non-interactive contexts","Allocate a TTY (ssh -t, docker run -it) when interactive confirmation is desired","Pipe scripts should always use --force"],"exampleFix":"# before (CI script)\nomx agent remove reviewer\n\n# after\nomx agent remove reviewer --force","handlingStrategy":"validation","validationCode":"const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);\nif (!interactive && !force) {\n  // either pass force:true or abort with a clear message before calling\n  force = true;\n}","typeGuard":"const isInteractive = (): boolean => Boolean(process.stdin.isTTY && process.stdout.isTTY);","tryCatchPattern":"try { await removeNativeAgent(name, { force: !isInteractive() }); } catch (e) { if (/interactive terminal/.test(String(e))) throw new Error('rerun with --force in CI'); throw e; }","preventionTips":["Always pass --force in CI/scripts/docker","Gate interactive prompts on isTTY checks in your own tooling","Use ssh -t or docker -it when you genuinely need the prompt"],"tags":["cli","agents","interactive","tty","ci"],"backgroundTag":"non-interactive-terminal","analyzedSha":"3ad79a8a6fe6e95fdbb8c00e40716fffe4011ce2","analyzedAt":"2026-08-27T22:18:39.783Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}