|
| 1 | +# Case Study: Issue #76 - CLI Hangs When No Arguments Provided |
| 2 | + |
| 3 | +## Executive Summary |
| 4 | + |
| 5 | +**Issue:** [#76](https://github.com/link-assistant/agent/issues/76) |
| 6 | +**Severity:** Medium (UX issue - application hangs but doesn't crash) |
| 7 | +**Root Cause:** Missing TTY detection and user feedback when stdin is connected to an interactive terminal |
| 8 | +**Status:** Solution designed and implemented |
| 9 | + |
| 10 | +## Timeline of Events |
| 11 | + |
| 12 | +### 2025-12-19 |
| 13 | + |
| 14 | +- **21:22:37 UTC** - User `andchir` reports issue #76 |
| 15 | + - Title (Russian): "Если нет аргументов, показывать справку по командам" |
| 16 | + - Translation: "If there are no arguments, show help for commands" |
| 17 | + - Description: "Currently nothing happens, hangs" |
| 18 | + |
| 19 | +### 2025-12-20 |
| 20 | + |
| 21 | +- **09:34:47 UTC** - AI solver begins work on issue #76 |
| 22 | +- **09:35:08 UTC** - PR #79 created as draft |
| 23 | +- **09:44:56 UTC** - Initial solution draft completed (timeout-based approach) |
| 24 | +- **10:11:52 UTC** - User `konard` provides feedback requesting: |
| 25 | + 1. Add `-p`/`--prompt` flag (with `--no-stdin-stream` behavior by default) |
| 26 | + 2. Add `--no-stdin-stream` flag |
| 27 | + 3. No timeout by default (keep `--stdin-stream-timeout` as optional) |
| 28 | + 4. Output JSON status message when entering stdin listening mode |
| 29 | + 5. Include CTRL+C + --help guidance in startup message |
| 30 | + 6. Create case study documentation |
| 31 | +- **10:15:17 UTC** - Work session resumed to address feedback |
| 32 | + |
| 33 | +## Problem Description |
| 34 | + |
| 35 | +### User Report (Translated) |
| 36 | + |
| 37 | +> If there are no arguments in the `agent` call, show command help. Currently nothing happens, hangs. |
| 38 | +
|
| 39 | +### What User Expected |
| 40 | + |
| 41 | +- Running `agent` without arguments should display help text |
| 42 | +- Clear indication of what the CLI expects |
| 43 | + |
| 44 | +### What Actually Happened |
| 45 | + |
| 46 | +- CLI hangs indefinitely waiting for stdin input |
| 47 | +- No feedback to user about what is expected |
| 48 | +- User must manually kill the process (CTRL+C) |
| 49 | + |
| 50 | +## Root Cause Analysis |
| 51 | + |
| 52 | +### Technical Root Cause |
| 53 | + |
| 54 | +The CLI was designed with stdin-first approach, expecting piped input: |
| 55 | + |
| 56 | +```javascript |
| 57 | +// Original behavior (problematic) |
| 58 | +const input = await collectStdin(); // Blocks forever on TTY |
| 59 | +``` |
| 60 | + |
| 61 | +This design works well for programmatic usage (`echo "hi" | agent`) but fails for interactive usage (`agent` in terminal). |
| 62 | + |
| 63 | +### UX Root Cause |
| 64 | + |
| 65 | +1. **Missing TTY Detection**: No check for `process.stdin.isTTY` |
| 66 | +2. **No User Feedback**: When waiting for stdin, nothing is output to inform the user |
| 67 | +3. **No Alternative Input Method**: No `-p`/`--prompt` flag for direct input |
| 68 | +4. **Silent Waiting**: The "nothing happens" experience is confusing |
| 69 | + |
| 70 | +### Industry Best Practices Violated |
| 71 | + |
| 72 | +According to [CLI Guidelines (clig.dev)](https://clig.dev/): |
| 73 | + |
| 74 | +> "If your command is expecting to have something piped to it and stdin is an interactive terminal, display help immediately and quit. This means it doesn't just hang, like cat." |
| 75 | +
|
| 76 | +According to [12 Factor CLI Apps](https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46): |
| 77 | + |
| 78 | +> "Never require a prompt. Always provide a way of passing input with flags or arguments." |
| 79 | +
|
| 80 | +## Proposed Solutions |
| 81 | + |
| 82 | +### Solution 1: TTY Detection with Immediate Help (Initial Implementation) |
| 83 | + |
| 84 | +**Status:** Partially implemented in initial commit |
| 85 | + |
| 86 | +When stdin is a TTY and no prompt is provided: |
| 87 | + |
| 88 | +- Show help text |
| 89 | +- Exit immediately |
| 90 | + |
| 91 | +```javascript |
| 92 | +if (process.stdin.isTTY && !argv.prompt) { |
| 93 | + yargsInstance.showHelp(); |
| 94 | + process.exit(0); |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +**Pros:** |
| 99 | + |
| 100 | +- Simple |
| 101 | +- Follows CLI best practices |
| 102 | +- Immediate user feedback |
| 103 | + |
| 104 | +**Cons:** |
| 105 | + |
| 106 | +- Doesn't allow interactive JSON input mode |
| 107 | +- Too aggressive for some use cases |
| 108 | + |
| 109 | +### Solution 2: Comprehensive Stdin Handling (Requested Implementation) |
| 110 | + |
| 111 | +**Status:** Implemented |
| 112 | + |
| 113 | +Components: |
| 114 | + |
| 115 | +1. **`-p`/`--prompt` flag**: Direct prompt input, bypasses stdin |
| 116 | +2. **`--disable-stdin` flag**: Disable stdin waiting explicitly |
| 117 | +3. **`--stdin-stream-timeout`**: Optional timeout for stdin reading |
| 118 | +4. **`--dry-run` flag**: Simulate operations without API calls |
| 119 | +5. **JSON startup message**: When entering stdin mode, output status: |
| 120 | + ```json |
| 121 | + { |
| 122 | + "type": "status", |
| 123 | + "message": "Agent CLI in stdin listening mode. Accepts JSON and plain text input.", |
| 124 | + "hint": "Press CTRL+C to exit. Use --help for options." |
| 125 | + } |
| 126 | + ``` |
| 127 | +6. **No default timeout**: Allow unlimited stdin input time |
| 128 | +7. **Optional `--stdin-stream-timeout`**: For users who want timeout behavior |
| 129 | + |
| 130 | +**Pros:** |
| 131 | + |
| 132 | +- Clear user communication |
| 133 | +- Flexible for different use cases |
| 134 | +- Follows industry best practices |
| 135 | +- Scriptable and interactive |
| 136 | + |
| 137 | +**Cons:** |
| 138 | + |
| 139 | +- More complex implementation |
| 140 | +- More flags to document |
| 141 | + |
| 142 | +### Decision Matrix |
| 143 | + |
| 144 | +| Scenario | Current Behavior | Solution 1 | Solution 2 (Recommended) | |
| 145 | +| ----------------------- | ---------------- | ----------------- | -------------------------- | |
| 146 | +| `agent` in terminal | Hangs forever | Shows help, exits | Shows help, exits | |
| 147 | +| `echo "hi" \| agent` | Works | Works | Works | |
| 148 | +| `agent -p "hello"` | N/A | N/A | Processes prompt directly | |
| 149 | +| `agent --disable-stdin` | N/A | N/A | Shows error, suggests -p | |
| 150 | +| `agent` with stdin open | Hangs forever | Hangs forever | Outputs status JSON, waits | |
| 151 | + |
| 152 | +## Implementation Plan (Completed) |
| 153 | + |
| 154 | +1. Add `-p`/`--prompt` option to yargs configuration |
| 155 | +2. Add `--disable-stdin` flag |
| 156 | +3. Add `--stdin-stream-timeout` optional flag |
| 157 | +4. Add `--dry-run` flag for testing |
| 158 | +5. Modify main() to check: |
| 159 | + - If `--prompt` provided: use that, don't wait for stdin |
| 160 | + - If `--disable-stdin`: show error if no prompt |
| 161 | + - If stdin is TTY and no prompt: show help |
| 162 | + - Otherwise: enter stdin listening mode with JSON status output |
| 163 | +6. Remove default timeout from stdin reading |
| 164 | +7. Output JSON status when entering stdin listening mode |
| 165 | +8. Add Flag.setDryRun() to flag module |
| 166 | + |
| 167 | +## Files Changed |
| 168 | + |
| 169 | +- `src/index.js` - Main CLI entry point |
| 170 | +- `src/flag/flag.ts` - Flag module with setDryRun() |
| 171 | +- `.changeset/fix-stdin-handling.md` - Changeset for release |
| 172 | + |
| 173 | +## Evidence Files |
| 174 | + |
| 175 | +- `issue-data.json` - Original issue report |
| 176 | +- `pr-data.json` - PR details and comments |
| 177 | +- `solution-draft-log.txt` - AI solver execution log (5730 lines) |
| 178 | +- `research.md` - CLI best practices research |
| 179 | + |
| 180 | +## Lessons Learned |
| 181 | + |
| 182 | +1. **Always detect TTY**: Check `process.stdin.isTTY` before waiting for stdin |
| 183 | +2. **Never hang silently**: Output status information when waiting for input |
| 184 | +3. **Provide alternatives**: Flags like `-p`/`--prompt` for non-piped usage |
| 185 | +4. **Follow industry standards**: CLI best practices exist for good reasons |
| 186 | +5. **User feedback is critical**: Even "waiting for input..." is better than silence |
| 187 | + |
| 188 | +## References |
| 189 | + |
| 190 | +- Issue: https://github.com/link-assistant/agent/issues/76 |
| 191 | +- PR: https://github.com/link-assistant/agent/pull/79 |
| 192 | +- [Command Line Interface Guidelines](https://clig.dev/) |
| 193 | +- [12 Factor CLI Apps](https://medium.com/@jdxcode/12-factor-cli-apps-dd3c227a0e46) |
| 194 | +- [Node.js TTY Documentation](https://nodejs.org/api/tty.html) |
| 195 | +- [Node.js CLI Apps Best Practices](https://github.com/lirantal/nodejs-cli-apps-best-practices) |
0 commit comments