{"record":{"id":"ff25a974048b92bd","repo":"zeroclaw-labs/zeroclaw","slug":"description-is-required-when-stdin-is-not-a-tty","errorCode":null,"errorMessage":"--description is required when stdin is not a TTY","messagePattern":"--description is required when stdin is not a TTY","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/skills/mod.rs","lineNumber":1227,"sourceCode":"    Ok(())\n}\n\nfn prompt_for_description(description: Option<String>) -> Result<String> {\n    if let Some(d) = description\n        && !d.trim().is_empty()\n    {\n        return Ok(d);\n    }\n    if std::io::IsTerminal::is_terminal(&std::io::stdin()) {\n        let prompt: String = dialoguer::Input::new()\n            .with_prompt(\"Skill description (what it does, when to use it)\")\n            .interact_text()?;\n        if prompt.trim().is_empty() {\n            anyhow::bail!(\"description must not be empty\");\n        }\n        Ok(prompt)\n    } else {\n        anyhow::bail!(\"--description is required when stdin is not a TTY\");\n    }\n}\n\nfn open_in_editor(path: &std::path::Path) -> Result<()> {\n    let Some(editor) = editor_from_env_or_path() else {\n        anyhow::bail!(\"no editor found; set VISUAL or EDITOR\");\n    };\n    let status = std::process::Command::new(&editor).arg(path).status()?;\n    if !status.success() {\n        anyhow::bail!(\"{editor} exited with non-zero status\");\n    }\n    Ok(())\n}\n\nfn editor_from_env_or_path() -> Option<String> {\n    std::env::var(\"VISUAL\")\n        .ok()\n        .filter(|value| !value.trim().is_empty())","sourceCodeStart":1209,"sourceCodeEnd":1245,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/src/skills/mod.rs#L1209-L1245","documentation":"Thrown by prompt_for_description in the skill-creation CLI flow (src/skills/mod.rs). When no usable --description was given and stdin is not a TTY, the interactive dialoguer prompt cannot be shown, so the command aborts instead of hanging on an invisible prompt. It guarantees scripted or piped invocations always supply an explicit description.","triggerScenarios":"Running the skill-create command with stdin piped, redirected, or detached from a terminal (CI job, cron, docker exec without -t, another process spawning the CLI) without passing --description; passing --description with only whitespace while stdin is non-interactive.","commonSituations":"Automation wrappers and CI pipelines that call the CLI without a PTY; running inside containers or IDE task runners where no TTY is allocated; users piping input expecting it to be read as the description.","solutions":["Pass a non-empty --description flag on the command line","Run the command in a real terminal so the interactive prompt appears","If wrapping the CLI from another program, allocate a PTY (e.g. script -qc, docker run -t) or always supply the flag"],"exampleFix":"# before (stdin piped, fails)\necho | zeroclaw skills create deploy-skill\n# after\nzeroclaw skills create deploy-skill --description \"Deploys the web dashboard\"","handlingStrategy":"validation","validationCode":"# bash wrapper: always supply --description when not interactive\nargs=(\"$@\")\nif ! [ -t 0 ] && [[ \" $* \" != *\" --description\"* ]]; then\n  args+=(--description \"${SKILL_DESCRIPTION:?set SKILL_DESCRIPTION for non-interactive runs}\")\nfi\nzeroclaw skills create \"${args[@]}\"","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass --description in scripts, CI, and containers regardless of TTY","Detect interactivity early (test -t 0 in shell, IsTerminal in Rust) and branch before invoking the CLI","Never assume a prompt will appear when the command runs under another process"],"tags":["cli","stdin","tty","skills","interactive-prompt","rust"],"backgroundTag":"missing-required-flag","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}