Skip to content

fix(cli): use the static spinner when starting the embedding daemon so Ctrl-C keeps working - #368

Open
Fibilisim-Tekno wants to merge 1 commit into
Kaelio:mainfrom
Fibilisim-Tekno:fix/361-static-spinner-embedding-daemon
Open

Fibilisim-Tekno wants to merge 1 commit into
Kaelio:mainfrom
Fibilisim-Tekno:fix/361-static-spinner-embedding-daemon

Conversation

@Fibilisim-Tekno

Copy link
Copy Markdown

Fixes #361.

The problem

ktx ingest cannot be interrupted with Ctrl-C once the managed local-embeddings daemon has started.

ensureManagedLocalEmbeddingsDaemon used createCliSpinner, which returns the animated clack spinner when stdout is a TTY. @clack/core's block() takes stdin over through readline.createInterface, which calls setRawMode(true), and spinner.stop() never restores cooked mode.

A console in raw mode does not translate Ctrl-C into a signal at all, so no SIGINT is ever generated — which is why no signal handler anywhere in ktx can help. The daemon starts a few seconds into every ingest that uses scan.enrichment.embeddings.backend: sentence-transformers, so the terminal stays raw for the whole run.

The fix

One call site moves to createStaticCliSpinner, which clack.ts already documents for exactly this situation, directly above the function:

Stderr-only, non-animated spinner. Use this instead of createCliSpinner when the next step reads stdin in raw mode (an Ink TUI or a keypress wait): the animated clack spinner seizes stdin via @clack/core's block() and leaves it dirty […]

The static spinner writes to stderr only and never touches stdin. I also left a short comment at the call site so the choice is not "cleaned up" back to the animated helper later.

Tests

Two regression tests in packages/cli/test/managed-local-embeddings.test.ts. makeIo now takes an isTTY flag, because the existing helper had no isTTY at all — meaning every existing test already took the static path and none of them could have caught this.

The assertion is expect(io.stdout()).toBe('') with isTTY: true: the animated spinner renders its frames to stdout, the static one does not, so empty stdout on a TTY is the observable proof that stdin was never seized. The second test covers the same thing on the failure path, which also used the spinner.

Trade-off

This gives up the animated frames while the daemon starts (first run downloads the model). That seemed clearly worth an interruptible ingest, and it matches what the issue suggested. If you would rather keep the animation, the alternative is restoring cooked mode after stop() and error() — happy to switch if you prefer that shape.

Scope

The same pattern may exist at other createCliSpinner call sites followed by stdin readers; I did not touch them, to keep this to one logical change.

Verification

I do not have the Windows console setup from the issue, so I did not reproduce the 0x0208 input-mode measurement myself. What I verified is on the code path: the call site used the animated helper on a TTY, createStaticCliSpinner exists and writes only to stderr, and the new tests fail before this change and pass after. This work was prepared with AI assistance and reviewed before submitting; the claims above are checked against the files they name.

The managed local-embeddings daemon started through the animated clack
spinner, which takes stdin over via @clack/core's block() and calls
setRawMode(true). Nothing ever restores cooked mode, and a console in raw
mode does not translate Ctrl-C into a signal at all, so `ktx ingest`
became impossible to interrupt for the rest of the run.

clack.ts already documents this hazard directly above
createStaticCliSpinner, which writes to stderr only and never touches
stdin. Switch the call site to it.

Fixes Kaelio#361
@vercel

vercel Bot commented Sep 13, 2026

Copy link
Copy Markdown

@Fibilisim-Tekno is attempting to deploy a commit to the Kaelio Team on Vercel.

A member of the Team first needs to authorize it.

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: ktx ingest cannot be interrupted with Ctrl-C - the embedding-daemon spinner leaves the terminal in raw mode

1 participant