v0.2.1: key validation validated nothing, piped input crashed - #4
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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— butGET /modelsis a public endpoint. Confirmed against the live API: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 constructsOpenRouterProviderdirectly to validate before saving, soValidateKeyAsynclives on the concrete type rather thanIChatProvider.2. Piped or scripted input crashed the app
Spectre raises this whenever any standard stream is redirected, and nothing caught it — so
/modelsfrom a pipe killed the app with a raw exception name on screen, and so did first-run setup. The REPL prompt had already moved toConsole.ReadLinefor 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
yesand 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.jsonandconfig.jsonquarantined with a clean start ·/resetwarns and cancels correctly ·[red]in a reply renders literally · redirected output has zero escape sequences · 90 tests greenThree 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