Skip to content

feat(diagnostics): say what a shutdown that ran out of budget was still holding - #45

Merged
kkdev92 merged 1 commit into
mainfrom
feat/lifecycle-diagnostics-4.1.0
Aug 29, 2026
Merged

feat(diagnostics): say what a shutdown that ran out of budget was still holding#45
kkdev92 merged 1 commit into
mainfrom
feat/lifecycle-diagnostics-4.1.0

Conversation

@kkdev92

@kkdev92 kkdev92 commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

When the shutdown budget runs out, the framework abandons the remaining work and reports application.shutdownTimeout. Until now that diagnostic said which phase it stopped in and nothing more, so the question it raised — what was still holding on — had no answer available anywhere.

Its details now carry the phase, the budget, how long it waited, the hosted service inside its own stop, the services still up, the operations that never settled, and the resource scope tree:

{
  "phase": "stop-hook",
  "budgetMs": 3000,
  "elapsedMs": 3000,
  "hostedServices": { "started": ["sample.watcher"], "stopping": "sample.index" },
  "operations": [{ "id": "command:sample.refresh#7", "name": "sample.refresh", "kind": "command", "elapsedMs": 2984 }],
  "backgroundTasks": 1,
  "resources": { "name": "sample#resources", "size": 2, "children": [{ "name": "sample#resources/projects", "size": 1, "children": [] }] }
}

Ids, names and counts only. Command arguments, webview payloads and secret values stay out: a diagnostic ends up in whatever log gets pasted into an issue.

What is new

  • RegistrationScope.inspect() and ResourceScope.inspect() return a ScopeInspection — name, entry count, attached children. Safe to call at any point, including during disposal, which is when a shutdown wants to know.
  • createTestHost().inspect() gives a test the same view: the scope trees, the hosted services still up, the operations that never settled.
  • onDiagnostic is documented. It has been part of defineExtension since 3.0.0 and appeared in no guide; the guide now has a Diagnostics section listing the events and what each is useful for.

Compatibility

Additive. leaks() keeps its exact shape on purpose: putting the new fields there was the first design, and it broke the first extension it was tried on — the guide tells you to assert on the whole object, toEqual sees a new field, and the test fails for a reason unrelated to the extension. The information moved to a separate method instead.

RegistrationScope and ResourceScope gained a method. Both are handed to extension code by the framework and never implemented by it, so this is additive in practice; it is a minor rather than a patch because the interfaces are exported.

Verification

npm run quality and npm run verify:package pass; both host lanes pass on VS Code 1.135.0.

A published extension built on this package was run against this build with its own sources untouched: type-check, lint, 271 unit and integration tests, and its 13 real-Extension-Host cases all pass — including the leaks() assertion that the first design broke.

New tests cover the scope inspection in isolation, the timeout details naming a hosted service, an operation and a scope tree, a describeRemaining that throws not affecting the stop, and an operation being forgotten once it settles.

🤖 Generated with Claude Code

…ll holding

`application.shutdownTimeout` reported the phase it stopped in and nothing
else, which left the only question that matters unanswered: which hosted
service, which operation, which scope. Its details now carry the phase, the
budget, how long it waited, the hosted service inside its own `stop`, the ones
still up, the operations that never settled and the resource scope tree.

The Host supplies the scopes and the deadline; naming a hosted service or an
operation is the Application's job, so it passes a `describeRemaining` callback
in. That callback is treated like any other observer -- if it throws, the stop
pipeline carries on without the explanation.

Both are built on `RegistrationScope.inspect()` and `ResourceScope.inspect()`,
which report a scope's name, its entry count and its attached children.

For tests, `createTestHost().inspect()` exposes the same view. `leaks()` is
deliberately unchanged: adding the fields there was the obvious move and it
broke the first extension it was tried on, because the guide tells you to
assert on the whole object and `toEqual` sees a new field. A separate method
costs one call and breaks nobody.

Ids, names and counts only. Command arguments, webview payloads and secret
values stay out of a diagnostic, because a diagnostic ends up in whatever log
gets pasted into an issue.

Also documents `onDiagnostic`, which has been part of `defineExtension` since
3.0.0 and appeared in no guide.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kkdev92
kkdev92 merged commit cfef3e1 into main Aug 29, 2026
10 checks passed
@kkdev92 kkdev92 mentioned this pull request Aug 29, 2026
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