agentleFS
MicrosoftKnown publishermicrosoft/vscode-documentdb/.github/skills/error-translation/SKILL.md

error-translation

Skill33 starsChanged 5 days agoMITView on GitHub

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.

The file

We split this skill into the rules it gives an agent

7 rules. About 0 are common, 6 are distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈6

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    No dialogs, no notifications, no progress bars, no starting or restarting anything, no retries, no prompts.

    line 20
  2. Distinctive:

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

    line 74
  3. Distinctive:

    Leave the error alone and none of this can break.

    line 104
  4. Very specific:

    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.

    line 108
  5. Distinctive:

    Do not assert what happened.

    line 164
  6. Distinctive:

    Keep it to one paragraph. The message becomes the heading of a modal, where a bulleted block renders as several lines of bold text.

    line 167
  7. Distinctive:

    No em dashes or en dashes.

    line 171

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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, next to the existing ones.

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:

ApproachExampleWhen
Look it up in state you already keepQuick Start reads listStatuses()Your source has a live registry
Inspect the connection stringAtlas checks the mongodb.net host suffixThe endpoint identifies the source
Record it while preparing the connectionKubernetes remembers clusterId in ensureReachableOnly 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.

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:

SurfaceFile
Every tree view, below a clusterBaseExtendedTreeDataProvider.ts
Cluster connect and list databasesClusterItemBase.ts
Shell connect bannerDocumentDBShellPty.ts
Query playgroundexecutePlaygroundCode.ts
Tree-node commands (create, drop, …)commandErrorHandling.ts
Any webview, via common.explainOperationFailureappRouter.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:

.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.
// 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
  • connectionReachabilityService.ts prepares a connection before connecting; this service explains a failure afterwards
  • tree-cluster-architecture for clusterId versus treeId
  • telemetry-instrumentation

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.