Skip to content

fix(chat): every reveal of the chat view handles its own rejection - #77

Merged
ndemianc merged 2 commits into
developfrom
fix/unhandled-focus-rejections
Aug 16, 2026
Merged

fix(chat): every reveal of the chat view handles its own rejection#77
ndemianc merged 2 commits into
developfrom
fix/unhandled-focus-rejections

Conversation

@ndemianc

Copy link
Copy Markdown
Contributor

Follow-up to #76, which fixed the two instances in the code it introduced and deliberately left the older ones alone to keep that review diff readable.

vscode.commands.executeCommand returns a Thenable, so a bare call in a void context turns any rejection into an unhandled promise rejection in the extension host — attributed to nothing, which is the part that makes it useless.

Six sites, not seven

The follow-up note listed the levelcode.ai.focus registration as needing a return. It doesn't:

vscode.commands.registerCommand('levelcode.ai.focus', () => vscode.commands.executeCommand('levelcodeAi.chat.focus')),

That's a concise arrow body, so it already returns the thenable and VS Code already reports its failures. Left alone — but now covered by the guard below, which is arguably more useful than changing it would have been.

The other six are all background reveals

addSelection, addContext, resumeSession, the editor panel's onDidDispose, the sessions view's newSession, and handleLaunch.

In every one, the accompanying work — a selection added, a session resumed, a login launched — has already succeeded by the time the reveal runs. Failing that work because the panel wouldn't come forward would be worse than the panel not coming forward. So they route through one helper that logs and never rejects:

function focusChatView(why) {
	return Promise.resolve(vscode.commands.executeCommand('levelcodeAi.chat.focus'))
		.then(undefined, (e) => dbg('chat.focus.failed', { why, msg: String((e && e.message) || e) }));
}

Logged, not swallowed. The complaint was "attributed to nothing", so .catch(() => {}) would answer the letter of it and none of the substance — why names the caller.

One guard for the whole class

Rather than six assertions naming six functions, the test scans every occurrence of the call and accepts only the handled forms: returned, awaited, concise-arrow, helper-wrapped, or inline-handled. That covers the seventh site and the eighth nobody has written yet — which matters, because this pattern reached six copies precisely because nothing was watching for it.

Verified non-vacuous against develop: the same scan reports exactly the six bare sites there and zero here.

bare sites on develop: 6
  line 423, 468, 1055, 2298, 2435, 2531

Bypasses

Each reverted and confirmed to fail:

bypass caught
a single site returned to a bare call
the switch-case site returned to a bare call
the focus command wrapped in a block body so it stops returning ✓ (proves the guard protects the site that was already correct)
the helper swallowing silently
the log dropping its caller attribution

One assertion deliberately relaxed after bypassing it. I'd pinned .then(undefined, …) over .catch(…) — but those are equivalent here, so the pin would have failed a refactor that changes nothing. It now accepts either form and still fails when the handler stops logging (verified both directions).

23 tests in chatSurface, 32 suites green.

Follow-up to #76, which fixed the two instances in the code it introduced and left the
older ones alone to keep that review diff readable.

`executeCommand` returns a Thenable, so a bare call in a void context turns any rejection
into an unhandled promise rejection in the extension host — attributed to nothing, which
is the part that makes it useless.

SIX SITES, NOT SEVEN. The brief for this change listed the `levelcode.ai.focus`
registration as needing a `return`. It does not: `() => vscode.commands.executeCommand(…)`
is a concise arrow body, so it already returns the thenable and VS Code already reports its
failures. Left alone.

The other six are all BACKGROUND reveals — addSelection, addContext, resumeSession, the
editor panel's onDidDispose, the sessions view's newSession, and handleLaunch. In each the
accompanying work has already succeeded by the time the reveal runs, so failing that work
because the panel would not come forward would be worse than the panel not coming forward.
They route through one `focusChatView(why)` helper that logs and never rejects.

Logged, not swallowed: the complaint was "attributed to nothing", so `.catch(() => {})`
would answer the letter of it and none of the substance. `why` names the caller in the log.

ONE GUARD FOR THE WHOLE CLASS. Rather than six assertions naming six functions, the test
scans every occurrence of the call and accepts only returned / awaited / concise-arrow /
helper-wrapped / inline-handled forms. That covers the seventh site and the eighth nobody
has written yet — which matters, because this pattern reached six copies precisely because
nothing was watching for it.

Verified non-vacuous against develop: the same scan reports exactly the six bare sites
there (lines 423, 468, 1055, 2298, 2435, 2531) and zero here.

Bypasses, each reverted and confirmed to fail:
  - a single site returned to a bare call; the switch-case site returned to a bare call
  - the focus command wrapped in a block body so it stops returning (proves the guard
    protects the site that was already correct)
  - the helper swallowing silently; the log dropping its caller attribution

One assertion deliberately relaxed after bypassing it: pinning `.then(undefined, …)` over
`.catch(…)` failed a refactor with no behavioural difference. It now accepts either and
still fails when the handler stops logging.

23 tests in chatSurface, 32 suites green.
Copilot AI lite review requested due to automatic review settings August 16, 2026 00:22

Copilot AI 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.

Pull request overview

This PR hardens levelcode-ai chat view “reveal/focus” calls so background reveals never create unhandled promise rejections in the extension host, and adds a test guard to prevent regressions.

Changes:

  • Introduces focusChatView(why) to wrap vscode.commands.executeCommand('levelcodeAi.chat.focus') for fire-and-forget reveals and log failures with caller attribution.
  • Routes six background reveal sites through the shared helper instead of issuing bare executeCommand calls.
  • Adds a test that scans all executeCommand('levelcodeAi.chat.focus') call sites and enforces that each occurrence is handled (returned/awaited/helper-wrapped/inline-handled).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
extensions/levelcode-ai/extension.js Adds focusChatView() and replaces background bare focus calls with helper-wrapped calls to avoid unhandled rejections.
extensions/levelcode-ai/test/chatSurface.test.js Updates existing expectation and adds a repo-guarding test to enforce all focus calls are handled.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread extensions/levelcode-ai/extension.js
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@ndemianc
ndemianc merged commit 0951551 into develop Aug 16, 2026
1 check passed
@ndemianc
ndemianc deleted the fix/unhandled-focus-rejections branch August 16, 2026 00:39
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.

2 participants