Skip to content

fix: integrations pane reports failed discards and CLI error reasons - #16

Open
hkaltai wants to merge 3 commits into
mainfrom
fix/integrations-pane-followups
Open

hkaltai wants to merge 3 commits into
mainfrom
fix/integrations-pane-followups

Conversation

@hkaltai

@hkaltai hkaltai commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #12 (merged), addressing its review comments. The plugin's integrations pane:

  • Failed discards are reported: opening or cancelling a credential form says when the values entered earlier could not be cleared, instead of ignoring it.
  • One failure helper: failure text comes from one helper (failureText), so a CLI failure that answers with an error field shows that reason too. The workspace list and link paths compute it once.
  • Forms without variants: a form whose fields output has no variants loads as having none.
  • Notes are kept together: when clearing earlier input fails and the fields cannot be read either, both reasons are shown. A refused connect shows the CLI's error reason, and falls back to " could not be connected." when the CLI gives no reason.
  • Link workspace picker: a redundant wrapper is dropped. It still lists one workspace per line.
  • README: when you're signed out, Connect and Connect & sync are hidden (use Keep local). The old text said Connect behaves as Keep local.

Not included: clearing the open form and workspace picker at session.start. That hook also fires on a plugin reload or worker respawn, so it would close a form you're in the middle of and drop what you typed. New sessions already start with empty state.

Test plan

  • claude plugin validate plugins/altimate-code passes, and tsc is clean.
  • claude plugin test plugins/altimate-code: 160 pass, 0 fail. New tests cover: failed discard reported on open (next to a fields failure) and on cancel; a form without variants; a refused connect with an error field; a refused connect with no reason. Each fails on the code before its fix.
  • Existing plugin suites pass: tests/*.sh, and pytest with 65 passed.

Follow-ups (pre-existing, not changed here)

  • refresh builds its own failure text and ignores an error field (integrations.tsx:106). Use failureText there.
  • The raw failed sentinel can still reach the user from connectFound, fields, set, set-secret, open-token-page and discard when the CLI gives no reason and stderr is empty.
  • An empty message from discard with a non-zero exit is treated as success by discardForAction. This hasn't been seen from the CLI.
  • These fixes are listed under the released 1.4.0 entry and the plugin version is not bumped, so installs of 1.4.0 may not pick them up. Release decision.

🤖 Generated with Claude Code

- Opening or cancelling a form reports when entered values could not be
  cleared, instead of ignoring it.
- Failure text comes from one helper, so a CLI failure that answers with an
  error field shows that reason; a form without variants loads as having
  none; the workspace picker drops a redundant wrapper.
- README: when signed out, Connect and Connect & sync are hidden.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
const data = parseJson<T>(result.stdout)
const failure = result.exitCode === 0 ? null : (data?.message ?? firstLine(result.stderr.replace(ANSI, '')) ?? 'failed')
return { data, failure }
return { data, failure: result.exitCode === 0 ? null : failureText(result) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[SUGGESTION]: failureText re-parses the same stdout

runJson already parsed result.stdout into data, but failureText(result) parses it again (line 89) on every failed call, and listWorkspaces then parses it once more before calling failureText. Passing the parsed data into the helper would keep the single source of failure text without the repeated JSON.parse.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this. The second parse happens only on the failure path (a non-zero exit), and the payload is a small JSON object, so the cost is negligible. Passing parsed data into failureText would widen its signature for every caller, including the sync path, which reads stdout differently, and keeping one helper that takes the raw result is simpler.

await update($, form, () => null)
await update($, typed, () => ({}))
await update($, formMessage, () => note)
await update($, formMessage, () => [failure && `Could not clear earlier input: ${failure}`, note].filter(Boolean).join(' · ') || null)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[SUGGESTION]: Discard warning is lost when the fields read also fails

loadForm overwrites formMessage on failure (line 279), so when the discard above failed and fields fails, this "Could not clear earlier input" warning is replaced and never shown - the exact case this PR aims to surface. Consider setting this message after loadForm, or appending to the existing formMessage instead of overwriting it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0df2c6c. loadForm now adds its failure next to the existing formMessage through joinNotes instead of replacing it, so both notes show (Could not clear earlier input: store locked · catalog unavailable). Every loadForm caller sets or clears formMessage first, so notes don't pile up.

try {
const { data, failure } = await runJson<Form & { message?: string }>($, ['fields', open.provider, ...(open.variant ? ['--variant', open.variant] : []), '--json'])
if (data && Array.isArray(data.fields)) await update($, form, () => data)
if (data && Array.isArray(data.fields)) await update($, form, () => ({ ...data, variants: Array.isArray(data.variants) ? data.variants : [] }))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[SUGGESTION]: The new no-variants fallback has no test

The normalisation here prevents fields.variants.length (line 751) from throwing when the CLI omits variants, but the added test only exercises the error field and discard paths. A case whose fields reply omits variants would lock this behaviour in.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in 0df2c6c: a form without variants loads, and a refused connect that answers with an error field shows that reason. Its fields reply has no variants, and the test asserts the form renders. It fails on the pre-fix code.

@kilo-code-bot

kilo-code-bot Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • plugins/altimate-code/CHANGELOG.md
  • plugins/altimate-code/hooks/integrations.test.tsx
  • plugins/altimate-code/hooks/integrations.tsx

Incremental review of 8ec4eb7..0e13059: the earlier findings are addressed. loadForm now appends its failure through joinNotes, so the "Could not clear earlier input" warning survives a fields failure (line 279/281), and the combined-note assertion in the test covers it. submitForm now takes run.failure when the CLI's JSON carries no message (line 434), so a refused connect surfaces the CLI's error reason, while the bare failed sentinel still maps to the friendly <name> could not be connected. fallback. New tests cover the no-variants fields reply, the error-field refusal, and the reason-less refusal. The failureText re-parse note was declined with sound rationale (failure path only, small payload) and that code is unchanged, so it is not carried forward.

Previous Review Summary (commit 8ec4eb7)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 8ec4eb7)

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 3
Issue Details (click to expand)

SUGGESTION

File Line Issue
plugins/altimate-code/hooks/integrations.tsx 81 failureText re-parses stdout that runJson already parsed (and listWorkspaces parses before calling it)
plugins/altimate-code/hooks/integrations.tsx 308 The new "Could not clear earlier input" warning is overwritten by loadForm's error when fields also fails
plugins/altimate-code/hooks/integrations.tsx 278 No test covers fields output that omits variants
Files Reviewed (4 files)
  • plugins/altimate-code/CHANGELOG.md - 0 issues
  • plugins/altimate-code/README.md - 0 issues (signed-out behaviour matches the band/pane code)
  • plugins/altimate-code/hooks/integrations.test.tsx - 0 issues
  • plugins/altimate-code/hooks/integrations.tsx - 3 issues

The core changes are sound: error is now surfaced by the shared failure helper, variants is normalised before use, the discard failure is reported on both open and cancel, and removing the redundant wrapper Box keeps the picker column layout because the parent is already flexDirection="column".

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash-1017 · Input: 41.4K · Output: 15.7K · Cached: 569.3K

@hkaltai hkaltai left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Peer review (lead + Codex Sol 6.1, converged in 1 round): no blocking issues. failureText is a strict superset of the old inline expression, the variants normalisation and picker Box removal are safe, and the README matches the render guards.

Three MINORs are inline.

Nits (consolidated):

  • integrations.tsx:304-311: when the discard fails on open, the form still opens over the values that weren't cleared. withProgress clears the warning on the next form action, and Connect then uses those values. That's an observation, not a required fix; refusing to open or keeping the warning visible would be stricter.
  • Scope can't be verified against a ticket: the PR links #12 (a PR) and no Jira or issue. Consider attaching one.

(Approval would go here, but GitHub doesn't allow approving your own PR.)

await update($, form, () => null)
await update($, typed, () => ({}))
await update($, formMessage, () => note)
await update($, formMessage, () => [failure && `Could not clear earlier input: ${failure}`, note].filter(Boolean).join(' · ') || null)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MINOR (bug): the open-time discard warning is overwritten when the fields load fails.

await update($, formMessage, () => [failure && `Could not clear earlier input: ${failure}`, note].filter(Boolean).join(' · ') || null)
await update($, message, () => null)
await loadForm($, open)

loadForm's failure branch (else await update($, formMessage, () => failure ?? 'Could not read the credential fields.')) replaces formMessage. So when both discard and fields fail, the user never sees that earlier input could not be cleared. The new test hits exactly this case (discard → store locked, fields → catalog unavailable) and shows only the second.

Suggestion: have loadForm compose onto the existing value, e.g. update($, formMessage, current => [current, failure ?? '…'].filter(Boolean).join(' · ')), or keep the discard warning in its own atom.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0df2c6c (joinNotes in loadForm), as in the reply above. The test now asserts the combined note on open.

const data = parseJson<T>(result.stdout)
const failure = result.exitCode === 0 ? null : (data?.message ?? firstLine(result.stderr.replace(ANSI, '')) ?? 'failed')
return { data, failure }
return { data, failure: result.exitCode === 0 ? null : failureText(result) }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MINOR (logic): failureText now reads error, but submitForm never reaches it when the CLI returns JSON:

outcome = run.data ?? { status: 'failed', message: run.failure }

(integrations.tsx:429). For { "error": "backend unavailable" } with exit 1, run.data is non-null and has no message, so the form shows Jira could not be connected. and drops the reason. The gap predates this PR, but it means the claim that "a CLI failure that answers with an error field shows that reason" doesn't hold for Connect / Update / Keep local. connectFound handles it already (data?.message ?? failure).

Suggestion: outcome = run.data ? { ...run.data, message: run.data.message ?? run.failure } : { status: 'failed', message: run.failure }.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 0df2c6c: submitForm now falls back to run.failure when the JSON has no message. The follow-up review caught that this made a reason-less failure show the bare failed sentinel. 0e13059 maps 'failed' to the friendly <name> could not be connected., the same way listWorkspaces and linkWorkspace do, and adds a test. refresh has its own copy of this logic that ignores error. That copy predates this PR, so it is listed as a follow-up in the description.

const ui = await $.ui.mount({ ...PANE, surface: 'terminal' })
await ui.press({ key: 'jira-add' })
await clock.settle()
expect(await ui.find({ type: 'Text', text: /catalog unavailable/ })).toBeDefined()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

MINOR (testing): this asserts only the fields error. The open-path discard warning added in openForm has no assertion, and in this fixture it is in fact overwritten (see the comment on openForm).

expect(await ui.find({ type: 'Text', text: /catalog unavailable/ })).toBeDefined()

Suggestion: add a case where discard fails and fields succeeds, and assert /Could not clear earlier input: store locked/ after jira-add. Also add a fields reply without variants to cover the loadForm normalisation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added in 0df2c6c: the open-path warning is asserted (combined with the fields failure), plus a test for a form without variants. 0e13059 adds the reason-less connect fallback test. All three fail on the pre-fix code.

hkaltai and others added 2 commits November 4, 2026 18:39
…fused connect shows the CLI's error field

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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