Skip to content

Fix infinite loop when sift runs without piped input and improve Ctrl+C handling#38

Merged
timtatt merged 9 commits intomainfrom
copilot/fix-infinite-loop-sift-command
Nov 17, 2025
Merged

Fix infinite loop when sift runs without piped input and improve Ctrl+C handling#38
timtatt merged 9 commits intomainfrom
copilot/fix-infinite-loop-sift-command

Conversation

Copy link
Contributor

Copilot AI commented Nov 12, 2025

This pull request introduces several improvements and refactorings to the sift terminal UI tool, focusing on better error handling, improved signal handling, codebase simplification, and enhanced user experience. The most significant changes include a refactor of program and model initialization, improved handling of stdin and signals, and more robust error messaging for users.

Refactor and Program Initialization:

  • Replaces the SiftOptions struct with ProgramOptions and introduces SiftModelOptions for clearer separation of concerns and improved dependency injection, especially for test management. The NewSiftModel function now returns an error if required dependencies are missing. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL59-R43), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L30-R31), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L64-R75), [[4]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L77-R89), [[5]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L92-R101), [[6]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41L803-R812), [[7]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41L950-R964))

  • Refactors how test results are read from stdin: moves scanning and JSON parsing from the main sift logic to the TestManager.ScanStdin method, which now supports context cancellation and improved error reporting. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL115-R111), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL138-R136), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-bc9cba620c8935606c6015b2e08af3826790efe1f154048deab4c9702b953263R228-R285))

Signal and Input Handling:

  • Adds signal handling using signal.NotifyContext to gracefully handle interrupts (e.g., ctrl+c) and ensure proper cleanup. ([internal/sift/sift.goL95-R92](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL95-R92))

  • Adds a check for whether stdin is a terminal (i.e., no piped input) and provides a user-friendly warning and usage instructions if no input is detected. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL4-R29), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-fce5f4cb235e5e097e714247a4138f26119a0a9756952f59256597378a6775acR1-R12), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-2873f79a86c0d8b3335cd7731b0ecf7dd4301eb19a82ef7a1cba7589b5252261R4-R25))

User Experience and Key Bindings:

  • Refines key handling: separates Quit (q) and ForceQuit (ctrl+c) actions, ensuring that ctrl+c always exits immediately and prints the inline view, even from search mode. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-e0348d3f815605f6aed012b20cd9cd819bcbe5afa509d92cb4576877b7cf8ac8R27), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-e0348d3f815605f6aed012b20cd9cd819bcbe5afa509d92cb4576877b7cf8ac8L134-R141), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96R464-R466), [[4]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96R590-R595))

Documentation and Versioning:

  • Updates the README.md to reflect the removal of the -v flag from go test usage and bumps installation instructions to the latest version. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L14-R14), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L26-R37), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L56-R62))

  • Bumps the internal version to v0.12.2. ([internal/sift/version.goL3-R3](https://github.com/timtatt/sift/pull/38/files#diff-20350daa2807ccc505b56a8e535bce1635d0bb83cfff17d875634cb05a92c077L3-R3))

Other Improvements:

  • Minor style/color tweaks and test additions for new functionality. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-2469e6a89382021ee38c5490a0fb7379d565663ab78c3f44d8f3fc441e5eaaf3L13-R13), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41R4-R10))

Refactor and Program Initialization

  • Replaces SiftOptions with ProgramOptions and introduces SiftModelOptions for clearer dependency injection; NewSiftModel now returns an error if the test manager is missing, improving robustness and testability. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL59-R43), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L30-R31), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L64-R75), [[4]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L77-R89), [[5]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96L92-R101), [[6]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41L803-R812), [[7]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41L950-R964))
  • Moves stdin scanning and JSON parsing to TestManager.ScanStdin, which now supports context cancellation and better error handling. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL115-R111), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL138-R136), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-bc9cba620c8935606c6015b2e08af3826790efe1f154048deab4c9702b953263R228-R285))

Signal and Input Handling

  • Adds signal handling with signal.NotifyContext for graceful shutdowns on interrupts. ([internal/sift/sift.goL95-R92](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL95-R92))
  • Adds a check for piped input; prints a warning and usage instructions if no input is detected. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-420fe4c0c4e17fb4f6c20bac1e30e63b5f93d28a98c0fb32fd448d3b5d085f2dL4-R29), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-fce5f4cb235e5e097e714247a4138f26119a0a9756952f59256597378a6775acR1-R12), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-2873f79a86c0d8b3335cd7731b0ecf7dd4301eb19a82ef7a1cba7589b5252261R4-R25))

User Experience and Key Bindings

  • Separates Quit (q) and ForceQuit (ctrl+c) key bindings, ensuring immediate exit and inline output on force quit, even from search mode. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-e0348d3f815605f6aed012b20cd9cd819bcbe5afa509d92cb4576877b7cf8ac8R27), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-e0348d3f815605f6aed012b20cd9cd819bcbe5afa509d92cb4576877b7cf8ac8L134-R141), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96R464-R466), [[4]](https://github.com/timtatt/sift/pull/38/files#diff-79f3bab5b2202ede81f95ff892716b95ed9bb9013bfaf513d08f58fdf6867c96R590-R595))

Documentation and Versioning

  • Updates README.md for new usage (removes -v from go test), and bumps install instructions and internal version to v0.12.2. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L14-R14), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L26-R37), [[3]](https://github.com/timtatt/sift/pull/38/files#diff-b335630551682c19a781afebcf4d07bf978fb1f8ac04c6bf87428ed5106870f5L56-R62), [[4]](https://github.com/timtatt/sift/pull/38/files#diff-20350daa2807ccc505b56a8e535bce1635d0bb83cfff17d875634cb05a92c077L3-R3))

Other Improvements

  • Minor style/color tweaks and adds tests for new functionality. ([[1]](https://github.com/timtatt/sift/pull/38/files#diff-2469e6a89382021ee38c5490a0fb7379d565663ab78c3f44d8f3fc441e5eaaf3L13-R13), [[2]](https://github.com/timtatt/sift/pull/38/files#diff-3585d81b04ad9d476f1d10a5676466cd29da89f17fc14eedcdef5ce1a4039c41R4-R10))Running sift without piped input causes indefinite hang on "Compiling" or "Waiting for test results...". Ctrl+C does not terminate the process properly in interactive mode.

Changes

Stdin detection - Added IsStdinTerminal() to detect when stdin is a terminal (no piped input):

func IsStdinTerminal() bool {
    stat, err := os.Stdin.Stat()
    if err != nil {
        return false
    }
    return (stat.Mode() & os.ModeCharDevice) != 0
}

Warning and help display - When no stdin is provided, print warning and display CLI help text in main.go:

if sift.IsStdinTerminal() {
    fmt.Fprintln(os.Stderr, "Warning: no input provided")
    fmt.Fprintln(os.Stderr)
    ctx.PrintUsage(false)
    os.Exit(1)
}

Signal handling - Use signal.NotifyContext for graceful shutdown on SIGINT/SIGTERM:

ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer cancel()

Separate quit behaviors - Differentiate between q and ctrl+c key handling:

  • Added ForceQuit key binding for ctrl+c to exit immediately
  • q key switches from interactive to inline mode (preserves existing behavior)
  • ctrl+c exits immediately regardless of mode or state
  • Both keys now handle correctly in search mode

When run without input, sift now displays:

Warning: no input provided

Usage: sift [flags]

Flags:
  -h, --help               Show context-sensitive help.
  -d, --debug              enable debug view
  -r, --raw                disable prettified logs
  -n, --non-interactive    disable interactive mode
  -v, --version            print version

Normal piped input operation is unaffected.

Original prompt

This section details on the original issue you should resolve

<issue_title>[Bug] Infinite loop when just running sift with no input</issue_title>
<issue_description>Describe the bug
If you just run sift with no input, it hangs on the "Compiling" and/or "Waiting for test results..." stages, even if I press Ctrl+C in the terminal.

It appears that if I press Ctrl+C, it goes from the "Compiling" stage to the "Waiting for test results..." one, but doesn't go anywhere from that.

To Reproduce
Just run sift, with no piped input:

> sift

Expected behavior
The program should either not run or allow me to exit somehow, even if just by pressing CTRL+C.

Screenshots
This is a screen recording that should illustrate the behaviour:

Gravacao.de.Tela.2025-11-09.as.17.34.54.mov

Versions:

  • Go Version: 1.25.0
  • CLI Version: 0.12.1

Additional context
I'm running MacOS, and would be open to open a PR to handle SIGINT in the code.
</issue_description>

Comments on the Issue (you are @copilot in this section)

@timtatt Thanks for raising, I'll take a look!

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

- Add stdin terminal detection to exit early with helpful error message
- Add signal handling (SIGINT/SIGTERM) for graceful shutdown
- Add test for isStdinTerminal function

Co-authored-by: timtatt <10578290+timtatt@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix infinite loop when running sift with no input Fix infinite loop when sift runs without piped input Nov 12, 2025
Copilot AI requested a review from timtatt November 12, 2025 23:05
@timtatt
Copy link
Owner

timtatt commented Nov 12, 2025

@copilot if there is no stdin provided, I want us to print a warning and then also the help text for the CLI.

Don't add unnecessary comments

@timtatt
Copy link
Owner

timtatt commented Nov 12, 2025

@copilot rather than using a sigChannel, use a signal.NotifyContext instead

- Export IsStdinTerminal function for use in main
- Handle no-stdin case in main.go to print warning and help
- Remove stdin check from sift.Run() to avoid duplication
- Update test to use exported function name

Co-authored-by: timtatt <10578290+timtatt@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 12, 2025

@copilot if there is no stdin provided, I want us to print a warning and then also the help text for the CLI.

Don't add unnecessary comments

Updated in b0297f1. When no stdin is provided, sift now prints a warning message and displays the CLI help text.

Output when running sift without input:

Warning: no input provided

Usage: sift [flags]

Flags:
  -h, --help               Show context-sensitive help.
  -d, --debug              enable debug view
  -r, --raw                disable prettified logs
  -n, --non-interactive    disable interactive mode
  -v, --version            print version

Replace manual signal channel handling with signal.NotifyContext for cleaner signal handling.

Co-authored-by: timtatt <10578290+timtatt@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 12, 2025

@copilot rather than using a sigChannel, use a signal.NotifyContext instead

Updated in ef89f2b. Replaced the manual signal channel with signal.NotifyContext for cleaner signal handling:

ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
defer cancel()

This eliminates the need for the signal channel and goroutine while maintaining the same functionality.

@timtatt
Copy link
Owner

timtatt commented Nov 12, 2025

@copilot the signal interrupt is not working when bubbletea is running. Using q should go from interactive to inline mode. ctrl+c should exit immediately.

- Add ForceQuit key binding for ctrl+c to exit immediately
- Keep Quit key binding for q to switch to inline mode
- Handle ctrl+c in search mode to force quit
- Preserves existing q behavior while making ctrl+c work as expected

Co-authored-by: timtatt <10578290+timtatt@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 12, 2025

@copilot the signal interrupt is not working when bubbletea is running. Using q should go from interactive to inline mode. ctrl+c should exit immediately.

Fixed in 3a4c57c. Separated the behavior of q and ctrl+c:

q key: Switches from interactive to inline mode (preserves existing behavior)
ctrl+c: Exits immediately regardless of mode

Changes made:

  • Added ForceQuit key binding specifically for ctrl+c
  • ctrl+c now immediately sets quitting=true and returns tea.Quit
  • Also handles ctrl+c in search mode to force quit
  • Existing q behavior is preserved

Both keys now work as expected in interactive mode.

Copilot AI changed the title Fix infinite loop when sift runs without piped input Fix infinite loop when sift runs without piped input and improve Ctrl+C handling Nov 12, 2025
@timtatt timtatt marked this pull request as ready for review November 17, 2025 12:17
@timtatt timtatt merged commit d278417 into main Nov 17, 2025
1 check passed
@timtatt timtatt deleted the copilot/fix-infinite-loop-sift-command branch November 17, 2025 12:18
@jpillora
Copy link

In @copilot We Trust.

@timtatt
Copy link
Owner

timtatt commented Nov 17, 2025

In @copilot We Trust.

Hah! I wouldn't go that far yet

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Infinite loop when just running sift with no input

3 participants