Skip to content

v0.2.1: key validation validated nothing, piped input crashed - #4

Merged
corecompiled merged 1 commit into
mainfrom
fix/key-validation-and-prompts
Aug 3, 2026
Merged

v0.2.1: key validation validated nothing, piped input crashed#4
corecompiled merged 1 commit into
mainfrom
fix/key-validation-and-prompts

Conversation

@corecompiled

Copy link
Copy Markdown
Owner

Two defects that shipped in v0.2.0, found by exercising the CLI rather than reading it. Split out ahead of the GUI (#3) so the fixes don't wait on a UI review.

1. A bad key was accepted and saved

Validation called ListModelsAsync — but GET /models is a public endpoint. Confirmed against the live API:

/models   no auth   -> 200
/key      bad key   -> 401

So any string passed. The user saw "Key saved. You're ready to chat", then every message failed with an auth error advising /reset — which led straight back to the same screen. An inescapable loop for anyone who mistypes their key, which is precisely the first-run experience.

Now validates against /key. No contract change: key acquisition is provider-specific, and the host already constructs OpenRouterProvider directly to validate before saving, so ValidateKeyAsync lives on the concrete type rather than IChatProvider.

2. Piped or scripted input crashed the app

NotSupportedException: Cannot show selection prompt since the current terminal isn't interactive.

Spectre raises this whenever any standard stream is redirected, and nothing caught it — so /models from a pipe killed the app with a raw exception name on screen, and so did first-run setup. The REPL prompt had already moved to Console.ReadLine for exactly this reason; the menus, confirmations and key entry had not.

Prompts now degrade: a numbered list read from stdin instead of arrow keys, a typed yes/no instead of a keypress, a plain read where there's nothing on screen to mask. Destructive confirmations require an explicit yes and treat EOF as no.

This corrects an earlier claim of mine. I described the AOT smoke test's non-zero exit as the app "correctly returning non-zero when setup was abandoned." It wasn't — it was this crash. I read the exit code and didn't check why.

Also

Choosing a model said the choice lasted "until you close OpenKey" — untrue since preferences became persistent.

Verified against the live API

Real key still works · bad keys rejected and not saved · corrupt session.json and config.json quarantined with a clean start · /reset warns and cancels correctly · [red] in a reply renders literally · redirected output has zero escape sequences · 90 tests green

Three regression tests cover the validation path, which no existing test would have caught — the old code passed every test it had.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGcarVYpt1mfjk6iFkcDpZ

Two defects shipped in v0.2.0, found by exercising the CLI rather than
reading it.

A mistyped or revoked key was accepted and saved. Validation called
ListModelsAsync, but GET /models is a *public* endpoint — it answers 200
with no Authorization header at all, so any string passed. The user saw
"Key saved. You're ready to chat", then every message failed with an auth
error whose advice was to run /reset, which led straight back to the same
screen. Confirmed against the live API: /models returns 200
unauthenticated while /key returns 401, so validation now uses /key.

No contract change. Key acquisition is provider-specific and the host
already constructs OpenRouterProvider directly to validate before saving,
so ValidateKeyAsync lives on the concrete type rather than IChatProvider.

Every remaining Spectre prompt threw when input was not a keyboard.
NotSupportedException("Cannot show selection prompt since the current
terminal isn't interactive") is raised whenever any standard stream is
redirected, and nothing caught it — so `/models` from a pipe killed the app
with a raw exception name on screen, and so did first-run setup. The REPL
prompt had already moved to Console.ReadLine for exactly this reason; the
menus, confirmations and key entry had not.

Prompts now degrade: a numbered list read from stdin instead of arrow keys,
a typed yes/no instead of a keypress, a plain read where there is nothing
on screen to mask. Destructive confirmations require an explicit "yes" and
treat EOF as no.

This also corrects an earlier misreading of the AOT smoke test. Its
non-zero exit was described as the app "correctly returning non-zero when
setup was abandoned". It was this crash.

Also: choosing a model claimed the choice lasted "until you close
OpenKey", which stopped being true when preferences became persistent.

Verified against the live API — real key still works, bad keys are
rejected and not saved, corrupt session and config files are quarantined
and the app starts clean, /reset warns and cancels correctly, markup in a
reply renders literally, redirected output contains zero escape sequences.

Released as 0.2.1 ahead of the GUI so the fixes do not wait on a UI review.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGcarVYpt1mfjk6iFkcDpZ
@corecompiled
corecompiled merged commit 7d1124f into main Aug 3, 2026
1 check passed
@corecompiled
corecompiled deleted the fix/key-validation-and-prompts branch August 3, 2026 07:30
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.

1 participant