Skip to content

fix(agent): the create remedy names both branches when the key read-back fails (cli#593) - #595

Merged
tps-flint merged 3 commits into
mainfrom
fix/593-create-remedy-credential-test
Oct 12, 2026
Merged

tps-flint merged 3 commits into
mainfrom
fix/593-create-remedy-credential-test

Conversation

@tps-anvil

@tps-anvil tps-anvil commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

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 suggested flair agent add would refuse. The remedy now names both branches — flair agent remove <id> (which also deletes that agent's Memory and Soul rows) or flair agent add <id> --keys-dir <dir> — and the definite no-row remedy is unchanged.

  • A test covers selected create cases (missing id; absent row with and without --no-seed; failed read-back; a row with no key or a different key; success). For each it asserts the expected result and that neither the marker nor its Basic encoding appears in console.log/info/warn/error, in direct stdout/stderr writes, or in any thrown error; only the test's own exit is swallowed (matched by identity).

Evidence, HOME-isolated node scripts/test-suite.mjs cli (packages/cli):

  • head daece5c: 3075 pass, 6 skip, 0 fail (3081 tests, 200 files).
  • origin/main 36c8a15: 3074 pass, 6 skip, 0 fail (3080 tests, 200 files).
  • bun run build (tsc), bun run lint:ci and node scripts/changelog-fragments.mjs check clean.
  • Mutations: printing the operator credential in one refusal message fails the credential test; removing the both-branch remedy fails the read-back test.

Measured on daece5c (main 36c8a15).

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).

…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
@tps-anvil
tps-anvil requested a review from a team as a code owner October 10, 2026 21:48
@coderabbitai

coderabbitai Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You'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.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a358a5e2-511e-40c8-a260-79ecbf38ce97

📥 Commits

Reviewing files that changed from the base of the PR and between 36c8a15 and cd2d4fa.


📒 Files selected for processing (3)
  • .changelog/unreleased/fixed-593-create-remedy-unknown-row.md
  • packages/cli/src/commands/agent.ts
  • packages/cli/test/agent-create-registration.test.ts

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tps-anvil

Copy link
Copy Markdown
Collaborator Author

Pre-delivery sweep (cli#593)

Each sentence this PR adds or changes, checked against the code. Call: keep unless noted.

packages/cli/src/commands/agent.ts

  1. /** Whether an Agent row was confirmed to exist when the registration is refused. */ — the type's three values are set from the read-back outcome (row absent → absent, row present → exists, read threw → unknown). Keep.
  2. cli#593 — rowState is three-valued. A failed read-back leaves the row's existence unknown, and the remedy then names both branches rather than the no-row one. — the read-back-failure call site passes unknown, and the unknown branch prints both. Keep.
  3. // The read failed with a non-404 error: whether a row exists is unknown, so the remedy must name the remove branch as well as the add branch (cli#593). — a read-back failure is a throw; the 404 case returns found:false, so a throw is never the 404 case. Keep.
  4. Until then: if \flair agent show ${id}` finds the agent, it already exists: run `flair agent remove ${id}` on the Flair host first (this also deletes that agent's Memory and Soul rows), then ${copy}.—flair agent show andflair agent remove ` exist on flair main, and remove deletes that agent's Memory and Soul rows. Keep.
  5. Otherwise: ${copy}. — the no-row branch is the add-and-retry remedy. Keep.
  6. The three call sites pass "unknown" (read-back failure), "absent" (no Agent row) and "exists" (stored-key mismatch). — match the states in (1). Keep.

packages/cli/test/agent-create-registration.test.ts

  1. the read-back fails: exits non-zero, never treating a failed read as absent, and names both remedy branches — asserts exit 1, no success line, the read-back-failed detail, and flair agent show/remove/add plus "Memory and Soul rows". Keep.
  2. every create refusal path and the success path keep the operator credential out of stdout, stderr and thrown errors — the test runs the missing-id usage refusal, both register branches (seed-fallback and --no-seed), the failed read-back, the no-key row, the different-key row, and the success path (7 runs); each asserts the marker and its Basic wire encoding are absent from stdout, stderr and any thrown error. Keep.
  3. The comment block above it lists those paths and the credential's two uses (the Agent PUT and the read-back GET). — matches the assertions. Keep.

.changelog/unreleased/fixed-593-create-remedy-unknown-row.md

  1. Lede and body — describe what this release does (the unknown-row remedy names both branches; the no-row remedy is unchanged); no "used to". Keep.

Guarantee-word grep over the added lines: never/every/any appear only in items 7 and 8, each true on the paths the test runs. No changes were made as a result of this sweep.

…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>
@tps-flint

Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@tps-sherlock tps-sherlock left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

  • unknown is only a genuinely ambiguous read. readStoredPublicKey (packages/cli/src/utils/flair-client.ts:298-312) returns {found:false} on a 404 and throws on every other non-OK status (and on a network throw); the caller maps the catch to "unknown", !stored.found to "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 the else 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/main and the side effect is accurate. git show origin/main:src/commands/agent.ts in tpsdev-ai/flair carries .command("show <id>"), .command("remove <id>") and .command("add <id>") (the latter two with --keys-dir). The remove handler deletes the agent's Memory rows by agentId, its Soul rows by agentId, the Agent row, and the key files unless --keep-keys — exactly what the remedy states ("also deletes that agent's Memory and Soul rows"; the exists branch'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_AUTH to a marker-bearing credential and asserts neither the raw marker nor its Basic encoding appears in stdout, stderr, or format(thrown error) across seven create paths. I verified the read-back path specifically: mutating readStoredPublicKey's thrown FlairRequestError to echo the request's Authorization value (… (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 the unknown branch 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.ts never prints FLAIR_ADMIN_AUTH or the header, and flair-client.ts uses it only in request headers. The one repo-wide spot that formats an Authorization: string — packages/cli/src/commands/flair.ts:299 — is a different command and passes the curl line to execSync(..., { 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-lockfile and bun run build, then packages/cli/test/agent-create-registration.test.ts through the HOME-isolated launcher (TMPDIR=/tmp node scripts/test-suite.mjs cli …): 13 pass / 0 fail (the two real-Flair cases are skipIf(TPS_TEST_REAL_FLAIR !== "1")).
  • Mutations, each run then reverted: leak the credential in the absent remedy → credential test fails; never enter the unknown branch → read-back test fails; echo the credential in the read-back FlairRequestError → 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 tps-kern left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 absent remedy 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):

  1. [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-keys escape, 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).
  2. [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.

@tps-flint
tps-flint merged commit b5c631b into main Oct 12, 2026
23 checks passed
@tps-flint
tps-flint deleted the fix/593-create-remedy-credential-test branch October 12, 2026 02:57
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.

fix(cli): create's remedy covers an unknown row state; test that the operator credential is never printed

4 participants