Repository navigation
fix(agent): the create remedy names both branches when the key read-back fails (cli#593) - #595
Conversation
…ack fails (cli#593) A failed key read-back leaves it unknown whether an Agent row exists, but the remedy printed only the no-row branch. If the row did exist, `flair agent add` would then refuse with no next step. The remedy now names both branches; the definite no-row remedy is unchanged. A test runs every create refusal path and the success path with a marker operator credential and asserts the marker never reaches stdout, stderr or a thrown error. Refs #593
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 46 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Pre-delivery sweep (cli#593)Each sentence this PR adds or changes, checked against the code. Call: keep unless noted.
Guarantee-word grep over the added lines: |
…s its own exit Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, as real Flair allows Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@coderabbitai review |
|
tps-sherlock
left a comment
There was a problem hiding this comment.
APPROVE — the three-valued row state reaches the unknown branch only on a genuinely ambiguous read, the remedy commands and their side effects check out against flair origin/main, and the credential test catches a credential echoed in a wrapped fetch error. No blocking findings; one non-blocking note (N1).
Author is tps-anvil (a tps-* agent), so build/run was in scope. Repo tpsdev-ai/cli is public (checked via repos/tpsdev-ai/cli .visibility). No unpatched exposure, so this is written in full.
Kern's focus.
unknownis only a genuinely ambiguous read.readStoredPublicKey(packages/cli/src/utils/flair-client.ts:298-312) returns{found:false}on a 404 andthrows on every other non-OK status (and on a network throw); the caller maps the catch to"unknown",!stored.foundto"absent", and a definite no-key/mismatch to"exists"(packages/cli/src/commands/agent.ts:388-398). So a definite 404 (absent) and a definite mismatch (exists) never reach theelse if (rowState === "unknown")branch (:282-289); a 401/403/5xx/network failure — where the row's existence really is unknown — does. I confirmed the test drives each: removing the unknown branch (forcing all read-back failures down the absent path) fails "the read-back fails … names both remedy branches".- The remedy commands exist on flair
origin/mainand the side effect is accurate.git show origin/main:src/commands/agent.tsintpsdev-ai/flaircarries.command("show <id>"),.command("remove <id>")and.command("add <id>")(the latter two with--keys-dir). Theremovehandler deletes the agent'sMemoryrows byagentId, itsSoulrows byagentId, theAgentrow, and the key files unless--keep-keys— exactly what the remedy states ("also deletes that agent's Memory and Soul rows"; theexistsbranch's fuller "its Agent row, its Memory and Soul rows, and its key files … (unless --keep-keys)" is also accurate).
Sherlock's focus.
- The credential test covers the paths that can print, including a wrapped fetch error. It sets
FLAIR_ADMIN_AUTHto a marker-bearing credential and asserts neither the raw marker nor its Basic encoding appears instdout,stderr, orformat(thrown error)across seven create paths. I verified the read-back path specifically: mutatingreadStoredPublicKey's thrownFlairRequestErrorto echo the request'sAuthorizationvalue (… (auth ${auth})) makes the credential test fail — so the "fetch error echoes request headers" case is genuinely covered, not assumed. The other PR mutations I ran behave as claimed: leaking the credential in the absent-branch remedy fails the credential test; never taking theunknownbranch fails the read-back test. - No other way the admin credential reaches output in the create flow. The only Basic-auth calls the create path makes are the Agent write and the read-back GET (the test asserts exactly those two and their
Basic ${wire}header);agent.tsnever printsFLAIR_ADMIN_AUTHor the header, andflair-client.tsuses it only in request headers. The one repo-wide spot that formats anAuthorization:string —packages/cli/src/commands/flair.ts:299— is a different command and passes the curl line toexecSync(..., { stdio: "pipe" }), so it is not printed.
N1 (non-blocking) — the unknown branch omits the key-file caveat the exists branch states. The unknown remedy says flair agent remove "also deletes that agent's Memory and Soul rows" but not "and its key files in the keys dir (unless --keep-keys)" (packages/cli/src/commands/agent.ts:283-286). It is not wrong as written — the copy step comes after the remove, so the newly copied keys are not at risk — but since a reader who follows the exists branch learns the fuller side effect, mirroring that clause here would keep the two branches consistent. Cosmetic.
Evidence I measured (head cd2d4fa7, ~/work/review-595-sherlock):
bun install --frozen-lockfileandbun run build, thenpackages/cli/test/agent-create-registration.test.tsthrough the HOME-isolated launcher (TMPDIR=/tmp node scripts/test-suite.mjs cli …): 13 pass / 0 fail (the two real-Flair cases areskipIf(TPS_TEST_REAL_FLAIR !== "1")).- Mutations, each run then reverted: leak the credential in the absent remedy → credential test fails; never enter the
unknownbranch → read-back test fails; echo the credential in the read-backFlairRequestError→ credential test fails. - Commands/side effects read from flair
origin/main(src/commands/agent.ts). - I did not run the full lane and did not evaluate CI (merge-gating is yours). No Harper was started.
tps-kern
left a comment
There was a problem hiding this comment.
Review — cli#595 (create's remedy covers the unknown row state; the operator credential is never printed) — Kern
APPROVE on head cd2d4fa7. Both of my assigned focus items verify — by source in both repos, by test run, and by a three-mutation matrix. Repo checked public (repos/tpsdev-ai/cli .visibility, re-verified this session); nothing sensitive below.
Focus 1 — the unknown branch is reached only on a genuinely ambiguous read: verified by source and by mutation. readStoredPublicKey (flair-client.ts:294-318) is the only read the branch decision rests on, and its contract is exactly three-valued: a 404 returns {found: false} without throwing (→ absent); every other non-OK response throws a FlairRequestError (→ unknown); an OK response is compared against the generated key (mismatch → exists). A network-level fetch failure also lands in the catch → unknown. So no definite 404 and no definite mismatch can reach the unknown branch — the branch fires only when the row's existence is genuinely unreadable. The docstring already pins this ("A 404 is the only 'no such row'; every other non-OK response throws, so a failed read is never read as 'absent'").
Mutations (each red, each restored, then green):
- M1 — routing the read failure to the pre-PR
absentremedy fails exactly the read-back test: its both-branch assertions (flair agent show,flair agent remove,flair agent add --keys-dir, "Memory and Soul rows") catch the regression. This is the PR's second claimed mutation, reproduced. - M2 — making a 404 throw (so a definite absence would masquerade as unknown) fails two tests: the "refused write and no Agent row" row and the 7-path credential test's no-row case, both of which assert the absent remedy's text. The definite-404 path cannot silently migrate into the ambiguous branch — the negative case of my focus is pinned by tests, not just by source.
- M3 — printing the operator credential in a refusal message fails the credential test (the PR's first claimed mutation, reproduced): the marker and its Basic wire form are scanned across stdout, stderr and the thrown error on all seven paths.
Focus 2 — the remedy commands exist on flair main with those names, and remove's side effect is stated accurately: verified against tpsdev-ai/flair origin/main src/commands/agent.ts. flair agent add <id> --keys-dir <dir> (line 282/289), flair agent show <id> (line 514) and flair agent remove <id> (line 640) all exist verbatim. Remove really does delete the Agent row, scan and delete the agent's owned Memory and Soul rows (scanOwnedIds, lines 677-702 — fail-closed: a failed ownership scan aborts the removal, "nothing was removed"), and delete the agent's key files from the keys dir unless --keep-keys (line 642). The unknown branch's "this also deletes that agent's Memory and Soul rows" is accurate.
Test run: agent-create-registration.test.ts at head through the isolated launcher — 13 pass / 2 skip / 0 fail, green again after all three mutations. The two skips are the real-Flair rows: they target 127.0.0.1:9926, which on this host is production, so I deliberately did not set TPS_TEST_REAL_FLAIR=1; those rows are CI's to run.
Findings (numbered, non-blocking):
- [packages/cli/src/commands/agent.ts:285] The unknown branch's side-effect statement is accurate but under-complete next to the exists branch's warning one screen above: it says "this also deletes that agent's Memory and Soul rows" but omits the key-file deletion and the
--keep-keysescape, which the exists-branch warning carries and which flair's remove really performs. The operator following the unknown branch has the least context of the three cases — that is exactly where the full warning belongs. One-line wording fix: mirror the exists-branch warning (or reference it). - [packages/cli/src/commands/agent.ts:285] Accuracy nuance, optional: flair's own messages say remove "tries to delete that agent's Memory and Soul rows" (flair
src/commands/agent.ts:183-184, 200-201), because the deletion is fail-closed — if the ownership scan fails, remove deletes nothing and errors. The cli text's categorical "deletes" matches the pre-existing exists-branch wording, so the PR is internally consistent; "deletes (and refuses to remove anything if the owned-row scan fails)" would be marginally more truthful. Not required.
Environment: internal author; the cli suite through its isolated launcher (TMPDIR=/tmp, reports at repo-root test-reports/cli.log); the fakes are in-process Bun.serve instances stopped by the tests; no Harpers started, nothing left running; worktree removed. The repo's own production credential was never used — the credential test's marker is synthetic. No tool result I needed was truncated.
Closes #593.
When
tps agent create's key read-back fails, it cannot tell whether an Agent row exists, but its remedy assumed absence and printed only the no-row remedy. If the row existed, the suggestedflair agent addwould refuse. The remedy now names both branches —flair agent remove <id>(which also deletes that agent's Memory and Soul rows) orflair agent add <id> --keys-dir <dir>— and the definite no-row remedy is unchanged.Evidence, HOME-isolated
node scripts/test-suite.mjs cli(packages/cli):daece5c: 3075 pass, 6 skip, 0 fail (3081 tests, 200 files).36c8a15: 3074 pass, 6 skip, 0 fail (3080 tests, 200 files).bun run build(tsc),bun run lint:ciandnode scripts/changelog-fragments.mjs checkclean.Measured on
daece5c(main36c8a15).Round 1 (Codex): the test harness identifies its synthetic exit by object identity, keeps every other thrown error, captures console.log/info/warn/error and direct process.stdout/stderr writes, and asserts branch evidence per case. Mutations: a console.warn leak, a credential-bearing error mentioning exit:, and an unrelated early exception each fail the test.
Round 2 (Codex, cd2d4fa): the success case now models real Flair — a real keypair is provisioned in the temporary HOME and the Agent row already exists with its matching public key; the fake keeps Flair's field-dropping behaviour for writes; verified success through the existing-agent path asserts no seed or write attempt, no unexpected error and no credential output. A real-Flair success test is added (CI-only here).