Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
95c6cd1
Make Quick Start reconciliation demand-driven
tnaum-ms Aug 7, 2026
72924eb
Show Docker details in Quick Start tooltips
tnaum-ms Aug 7, 2026
abbfa64
Handle stopped Quick Start connections
tnaum-ms Aug 7, 2026
811fa35
Make stopped Quick Start prompt modal
tnaum-ms Aug 7, 2026
06561b1
Improve stopped Quick Start prompt copy
tnaum-ms Aug 7, 2026
68f6d57
Explain infrastructure-caused connection failures
tnaum-ms Aug 9, 2026
2be9da8
Keep the shell terminal open after a failed connect
tnaum-ms Aug 9, 2026
35efb19
Translate tree failures in every view, not just Connections
tnaum-ms Aug 9, 2026
6c51fa6
Translate operation failures in webviews
tnaum-ms Aug 9, 2026
8b2a66e
Translate operation failures in commands and the Document view
tnaum-ms Aug 9, 2026
1432f41
fix(quickstart): keep Quick Start reachable when Docker is unavailable
tnaum-ms Aug 9, 2026
785d9ed
fix(diagnostics): never translate a cancellation into an infrastructu…
tnaum-ms Aug 9, 2026
604f72c
fix(quickstart): tell a stopped Docker daemon apart from a removed co…
tnaum-ms Aug 9, 2026
79f95b4
refactor(quickstart): give diagnostics a genuinely read-only preflight
tnaum-ms Aug 9, 2026
c510774
fix(atlas): keep the TLS diagnosis to one paragraph
tnaum-ms Aug 9, 2026
e38cefc
fix(tree): stop dropping the raw error on the non-modal diagnosis path
tnaum-ms Aug 9, 2026
447e047
fix(shell): redact cached credentials before logging a connect failure
tnaum-ms Aug 9, 2026
22bf3c0
fix(quickstart): answer a failed preflight with tree rows, not a modal
tnaum-ms Aug 9, 2026
b9a4705
perf(diagnostics): budget the whole explain call, not each provider
tnaum-ms Aug 9, 2026
7601922
fix(quickstart): show display labels in the managed-instance tooltip
tnaum-ms Aug 9, 2026
99e4ffd
fix(commands): keep argument unwrapping inside the guarded block
tnaum-ms Aug 9, 2026
2f37192
fix(quickstart): show progress during an explicit deep refresh
tnaum-ms Aug 9, 2026
2879053
docs(diagnostics): note that the error is a bare string on the webvie…
tnaum-ms Aug 9, 2026
599cbc8
chore: refresh localization bundle and settle types after the review …
tnaum-ms Aug 9, 2026
3ec3a95
docs: record the PR 876 review and its resolution
tnaum-ms Aug 9, 2026
1e127dd
fix(quickstart): stop re-inspecting the container hydration just adopted
tnaum-ms Aug 9, 2026
7830929
docs: record the redundant first-expansion probe in the PR 876 review
tnaum-ms Aug 9, 2026
db01208
Use the tree framework's node progress for Quick Start
tnaum-ms Aug 9, 2026
b39a52d
Keep Quick Start transitional rows in step with the progress overlay
tnaum-ms Aug 9, 2026
a96b77b
Keep the host out of localized Quick Start descriptions
tnaum-ms Aug 9, 2026
d971af0
Give the running Quick Start instance the cluster commands that apply
tnaum-ms Aug 9, 2026
aba688b
Report Quick Start situations as typed keys, not as English sentences
tnaum-ms Aug 9, 2026
6190ce6
Address review: typed key for a repeat resume timeout, keep copy arou…
tnaum-ms Aug 10, 2026
0f78cc7
Report Quick Start situations as typed keys, not as English sentences…
tnaum-ms Aug 10, 2026
0194ac2
Address review: close four gaps the reviewer found in this PR's own c…
tnaum-ms Aug 10, 2026
6bbc28b
Merge branch 'release/0.10.0' into dev/tnaum/quickstart-improvements
tnaum-ms Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ See `src/tree/models/BaseClusterModel.ts` and `docs/analysis/08-cluster-model-si

- [skills/tree-cluster-architecture/SKILL.md](skills/tree-cluster-architecture/SKILL.md) - Required patterns for cluster tree items, dual identity, provider lookup, and regression tests
- [skills/telemetry-instrumentation/SKILL.md](skills/telemetry-instrumentation/SKILL.md) - Telemetry instrumentation patterns
- [skills/error-translation/SKILL.md](skills/error-translation/SKILL.md) - Turning infrastructure failures into actionable messages; providers translate, they never show UI

## Terminology

Expand Down
187 changes: 187 additions & 0 deletions .github/skills/error-translation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
---
name: error-translation
description: How infrastructure-caused database failures are turned into messages users can act on, via ConnectionDiagnosticsService. Use when adding a discovery plugin, a connection source, a reachability provider, a new tree view or webview surface, when a user reports a confusing or raw driver error, or when asked to improve error messages for Docker, Kubernetes, Atlas, Azure or any other infrastructure-backed connection.
---

# Error Translation

A connection can fail for reasons that have nothing to do with the database: a container was
stopped, a port-forward tunnel died, a service closed the TLS handshake. The driver reports these
as `ECONNREFUSED`, a server-selection timeout, or an OpenSSL alert, none of which tell the user
what to do.

`ConnectionDiagnosticsService` lets the source that owns the infrastructure explain the failure in
its own words.

## The one rule

> **Providers translate. They never show UI and never recover.**

No dialogs, no notifications, no progress bars, no starting or restarting anything, no retries, no
prompts. A provider receives an error and returns text.

This is not a style preference. One user action often runs several database commands, several
actions can fail at the same time, and many calls happen on background paths that show nothing. A
provider that showed UI or repaired state would produce duplicate dialogs, dialogs nobody asked
for, and errors that are already obsolete by the time they appear.

Anything with a side effect belongs at the call site, which alone knows whether the user is
watching, whether the operation was a read or a write, and which surface is right.

## Adding a provider

Implement `ConnectionDiagnosticsProvider` and register it in
[ClustersExtension.registerDiscoveryServices](../../../src/documentdb/ClustersExtension.ts),
next to the existing ones.

```ts
export class MyDiagnosticsProvider implements ConnectionDiagnosticsProvider {
public readonly id = 'my-source';

public async explain({ clusterId, error }: ConnectionDiagnosticsRequest): Promise<string | undefined> {
if (!isMine(clusterId)) {
return undefined; // not ours: the caller shows the original error
}
if (!looksLikeMyFailure(error)) {
return undefined; // ours, but nothing wrong on our side
}
return l10n.t('...');
}
}
```

`undefined` is always the safe answer: it means "show the original error".

A provider may answer without inspecting the error at all. Cancellations are therefore filtered
centrally: `explain()` returns `undefined` for a `UserCancelledError` before any provider is asked,
so a wizard the user escaped is never reported as an infrastructure failure.

### Answer the cheap question first

`explain()` runs on every foreground failure across the whole extension, so the common case must
cost almost nothing. Order the checks so the fastest rejection happens first.

- `QuickStartDiagnosticsProvider` scans an in-memory list of managed instances before touching Docker.
- `AtlasDiagnosticsProvider` tests the error message shape before looking up any credentials.
- `KubernetesDiagnosticsProvider` checks its in-memory map before importing the tunnel machinery.

### Do not cache verdicts

`explain()` runs once per user-initiated failure, so caching buys nothing and costs correctness: a
memoized "not running" would still be reported right after the user starts the container and
retries. Probe fresh every time.

If a provider ever does become expensive enough to matter, share in-flight work rather than
caching results, so a repeat attempt still sees the current state.

### Knowing whether a cluster is yours

`clusterId` is the only identity that reaches every call site. Stored connection properties do not
travel past the tree item, so a webview, the shell and the playground cannot read them. Pick
whichever of these fits your source:

| Approach | Example | When |
| --- | --- | --- |
| Look it up in state you already keep | Quick Start reads `listStatuses()` | Your source has a live registry |
| Inspect the connection string | Atlas checks the `mongodb.net` host suffix | The endpoint identifies the source |
| Record it while preparing the connection | Kubernetes remembers `clusterId` in `ensureReachable` | Only the stored properties identify the source |

The third case is why `ConnectionReachabilityProvider.ensureReachable` takes an optional
`clusterId`: that call is the one moment where both halves are known.

## Never touch the error

`explain()` returns text. It does not modify, replace, or attach properties to the error, and
neither should you. A lot of code here inspects errors by identity rather than by text:

- `instanceof UserCancelledError` decides failure versus cancellation, in roughly 25 places;
- `instanceof QueryError`, `MongoBulkWriteError` and `SettingsHintError` change how a failure is handled;
- `error.code` is read for server codes (115, 235) and socket codes (`ECONNRESET`, `ENOTFOUND`);
- `errorCodeExtractor.ts` reads `error.cause.cause.code` at a **fixed depth**, so an extra wrapper level breaks Collection view error-code detection;
- `extractErrorCode()` parses a `[CODE-12345]` prefix from the **start** of a message, so prepending text breaks the shell and the playground;
- the tRPC boundary rebuilds errors as `{ code, name, message, stack, cause }`, so a custom property never reaches a webview anyway.

Leave the error alone and none of this can break.

## Adding a call site

Call `explain()` where you are about to **render** a failure, then show its message instead of the
raw one and keep the raw text as detail. Rethrow the original error unchanged so telemetry and
every downstream check keep working.

```ts
const diagnosis = await ConnectionDiagnosticsService.explain({ clusterId, error });
void vscode.window.showErrorMessage(diagnosis?.message ?? l10n.t('Failed to load ...'), {
modal: true,
detail: error instanceof Error ? error.message : String(error),
});
```

Existing call sites:

| Surface | File |
| --- | --- |
| Every tree view, below a cluster | [BaseExtendedTreeDataProvider.ts](../../../src/tree/BaseExtendedTreeDataProvider.ts) |
| Cluster connect and list databases | [ClusterItemBase.ts](../../../src/tree/documentdb/ClusterItemBase.ts) |
| Shell connect banner | [DocumentDBShellPty.ts](../../../src/documentdb/shell/DocumentDBShellPty.ts) |
| Query playground | [executePlaygroundCode.ts](../../../src/commands/playground/executePlaygroundCode.ts) |
| Tree-node commands (create, drop, …) | [commandErrorHandling.ts](../../../src/utils/commandErrorHandling.ts) |
| Any webview, via `common.explainOperationFailure` | [appRouter.ts](../../../src/webviews/_integration/appRouter.ts) |

Tree views need no per-view wiring: `wrapGetChildrenWithErrorAndStateHandling` translates on the way
out, so any provider built on the base class is covered. Commands registered with
`registerCommandWithTreeNodeUnwrappingAndModalErrors` are covered the same way, via the tree node
they receive.

### Do not call it from background paths

Background work shows nothing, so translating there costs I/O for no benefit. Leave these alone:
collection and document count badges, index count badges, the Collection view document count, and
the Query Insights stage 1 prefetch. They already swallow their errors on purpose.

### Webviews

An explanation cannot ride along on an error across the tRPC boundary, so a webview asks for one:

```tsx
.catch(async (error) => {
const cause = error instanceof Error ? error.message : String(error);
const explained = await trpcClient.common.explainOperationFailure.query({ message: cause });
void trpcClient.common.displayErrorMessage.mutate({
message: explained ?? l10n.t('Error while running the query'),
modal: true,
cause,
});
});
```

`explainOperationFailure` reads the `clusterId` from the webview's tRPC context and returns `null`
when nothing applies. Only the error MESSAGE crosses the boundary, so a provider that needs an
error's class or `code` cannot be served this way. Never wrap an error to smuggle text through.

## Writing the message

- Do not assert what happened. Say "we cannot find", "very likely", "does not appear to be".
- "We" is fine and is the established voice.
- Say what the user can do next, and where.
- Keep it to one paragraph. The message becomes the heading of a modal, where a bulleted block
renders as several lines of bold text; the raw driver error already occupies the detail area.
`AtlasDiagnosticsProvider` returns `summarizeAtlasTlsHandshakeRejection()` for this reason, while
the Discovery-view modal keeps the longer `describeAtlasTlsHandshakeRejection()` as its detail.
- No em dashes or en dashes.
- Wrap every string in `l10n.t()` and run `npm run l10n`.

```ts
// Good
l10n.t('We cannot find the DocumentDB Local container. It was very likely removed outside VS Code. You can recreate it from the Connections view, which reuses the existing data volume.')

// Bad: asserts a cause, and offers no next step
l10n.t('The container was removed outside VS Code.')
```

## Related

- [connectionDiagnosticsService.ts](../../../src/services/connectionDiagnosticsService.ts)
- [connectionReachabilityService.ts](../../../src/services/connectionReachabilityService.ts) prepares a connection *before* connecting; this service explains a failure *afterwards*
- [tree-cluster-architecture](../tree-cluster-architecture/SKILL.md) for `clusterId` versus `treeId`
- [telemetry-instrumentation](../telemetry-instrumentation/SKILL.md)
Loading