Skip to content

docs(components): document app cleanup on shutdown via scope 'close' - #647

Open
Ethan-Arrowood wants to merge 2 commits into
mainfrom
docs/shutdown-cleanup-scope-close
Open

docs(components): document app cleanup on shutdown via scope 'close'#647
Ethan-Arrowood wants to merge 2 commits into
mainfrom
docs/shutdown-cleanup-scope-close

Conversation

@Ethan-Arrowood

Copy link
Copy Markdown
Member

Summary

Shutdown cleanup for applications was technically documented but undiscoverable: the entire coverage was one line in the Scope events list in plugin-api.md — "Emitted after scope.close() is called" — which reads as if the plugin author calls scope.close() themselves. Nothing said Harper calls it, which is the whole reason an application would listen for the event.

  • reference/components/plugin-api.md — new ## Cleanup on Shutdown section with the scope.once('close', ...) pattern, stating that Harper calls scope.close() on shutdown and on graceful restart. Clarified the 'close' event bullet and the scope.close() entry in place, and linked both to the new section.
  • reference/components/applications.md — new ## Shutdown Cleanup pointer cross-linking to it. That page previously had zero occurrences of "cleanup", "shutdown", or "teardown", so the application-building audience had nothing to find.
  • Added a Version History entry for the v5.1.3 behavior change.

Source evidence

Verified in HarperFast/harper at v5.2.6 (latest release) before writing:

  1. The loader calls scope.close() on shutdowncomponents/componentLoader.ts:700:
    onMessageByType(ITC_EVENT_TYPES.SHUTDOWN, () => trackScopeClose(scope.close()));
  2. Graceful restart uses the same pathrestartWorkers posts the same SHUTDOWN ITC message (server/threads/manageThreads.js:555-559), reached from bin/restart.ts:187 (restart operation) and manageThreads.js:1218 (dev watch reload).
  3. Async 'close' listeners are awaitedcomponents/Scope.ts:275-288 collects this.listeners('close') and await Promise.all(...) over their return values.
  4. Exit waits for scope cleanupserver/threads/threadServer.js:205-211: .then(() => closeServers()).then(() => whenScopesClosed()).then(() => realExit(0)), backed by the components/scopeShutdown.ts registry (which logs a rejected close and treats it as settled, so failed cleanup can't wedge shutdown).
  5. Cleanup is bounded — worker self-exit backstop at threadTerminationTimeout (10s; 30s under DEV_MODE) in manageThreads.js:87-105, with the main thread force-terminating at 2x (manageThreads.js:585).

Status of HarperFast/harper#1912

Still open, but the code it describes has already been fixed and released — so this PR documents the corrected behavior rather than the caveat the issue asked for.

harper#1912 cites Scope.ts:181-187 (bare this.emit('close')), componentLoader.ts:472 (fire-and-forget scope.close()), and threadServer.js:175-177 (closeServers().then(() => realExit(0))). That is the pre-fix layout. Both halves of its "Suggested direction" landed in commit d72d5b0 ("fix: dispose component scopes before worker exit in dev, and serialize reloads", harper#1323).

Bisecting the release tags for the awaited-listener code in components/Scope.ts:

Releases Behavior
v5.0.0 - v5.1.2 bare emit('close') — async cleanup raced process.exit
v5.1.3 - v5.2.6 awaits 'close' listener promises

So the docs state that async cleanup is awaited, with the remaining genuine caveat being the termination backstop. harper#1912 looks stale and is probably closable — flagging rather than acting on it here.

Notes (not addressed in this PR)

npm run build reports two pre-existing broken anchors in files this PR does not touch:

  • /reference/v5/backups/overview -> /reference/v5/cli/commands#backing-up-with-volume-snapshots
  • /release-notes/v5-lincoln/5.1 -> #deployment-operations

Also, the repo's committed node_modules state was missing @harperfast/code-guidelines, so format:check fails in a stale checkout until npm ci is re-run.

Verification

  • npm run format:check — clean
  • npm run build — succeeds; all anchors added by this PR validate

Closes #604

🤖 Generated with Claude Code

@Ethan-Arrowood
Ethan-Arrowood requested a review from a team as a code owner August 27, 2026 20:40

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request adds documentation for application shutdown cleanup and updates the Plugin API reference to detail how scope.close() and 'close' event listeners handle asynchronous cleanup in version 5.1.3. The review feedback suggests style guide corrections to use hyphens instead of em dashes as field separators, and recommends adding a <VersionBadge> component to highlight the behavior change in scope.close().

#### Events

- **`'close'`** — Emitted after `scope.close()` is called
- **`'close'`** — Emitted after `scope.close()` is called. Harper calls `scope.close()` itself on shutdown and on graceful restart, so this is the hook for application teardown — see [Cleanup on Shutdown](#cleanup-on-shutdown)

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.

medium

According to the general rules, hyphens (-) should be used instead of em dashes () as field separators in Markdown documentation (e.g., separating configuration option names from their descriptions). Please update the 'close' event description to use a hyphen.

Suggested change
- **`'close'`** Emitted after `scope.close()` is called. Harper calls `scope.close()` itself on shutdown and on graceful restart, so this is the hook for application teardown — see [Cleanup on Shutdown](#cleanup-on-shutdown)
- **`'close'`** - Emitted after `scope.close()` is called. Harper calls `scope.close()` itself on shutdown and on graceful restart, so this is the hook for application teardown — see [Cleanup on Shutdown](#cleanup-on-shutdown)
References
  1. In Markdown documentation, use hyphens ('-') instead of em dashes ('—') as field separators (e.g., separating configuration option names from their descriptions) to adhere to the style guide, even if the file has pre-existing inconsistencies using em dashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not taking this one. The em-dash rule is being over-generalized here.

AGENTS.md says em dashes "are fine for parenthetical asides and match existing prose; do not use them as field separators." The field-separator prohibition refers to the bullet immediately above it - Type: / Default: reference blocks, where each field goes on its own line. It is not a ban on - **term** — description in prose lists.

Two checks: the docs tree has 633 em-dash list-item lines across 63 reference files, and AGENTS.md itself uses this exact pattern - including in the rule being quoted (- **Hyphens vs. dashes** — em dashes...).

So these are the established convention, not "pre-existing inconsistencies." Applying the suggestion would leave the touched lines inconsistent with every sibling line in the same list.

sent with Claude Opus 5

Comment on lines 190 to +192
#### `scope.close()`

Closes all associated entry handlers and the `scope.options` instance, emits `'close'`, and removes all listeners.
Closes all associated entry handlers and the `scope.options` instance, emits `'close'`, and removes all listeners. Promises returned by `'close'` listeners are awaited before it resolves.

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.

medium

When documenting behavior changes to an existing API surface, we should use the component for standalone placement after headings. Since the behavior of scope.close() awaiting 'close' listener promises was introduced in v5.1.3, let's add a right after the heading.

Suggested change
#### `scope.close()`
Closes all associated entry handlers and the `scope.options` instance, emits `'close'`, and removes all listeners.
Closes all associated entry handlers and the `scope.options` instance, emits `'close'`, and removes all listeners. Promises returned by `'close'` listeners are awaited before it resolves.
#### `scope.close()`
<VersionBadge type="changed" version="v5.1.3" />
Closes all associated entry handlers and the `scope.options` instance, emits `'close'`, and removes all listeners. Promises returned by `'close'` listeners are awaited before it resolves.
References
  1. When documenting behavior changes to an existing API surface (e.g., adding new fields to a response), use .
  2. Reserve the component for standalone placement after headings. For inline version annotations mid-sentence or inside list items, use plain text (e.g., (vX.Y.Z)) instead, as using the component mid-sentence is awkward.


- **v4.6.0** — Plugin API introduced (experimental)
- **v4.7.0** — Further improvements to the Plugin API
- **v5.1.3** — Shutdown awaits promises returned by `'close'` listeners; earlier releases emitted `'close'` without waiting, so async cleanup could be cut off by the worker exiting

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.

medium

According to the general rules, hyphens (-) should be used instead of em dashes () as field separators in Markdown documentation. Please update the v5.1.3 version history entry to use a hyphen.

Suggested change
- **v5.1.3** Shutdown awaits promises returned by `'close'` listeners; earlier releases emitted `'close'` without waiting, so async cleanup could be cut off by the worker exiting
- **v5.1.3** - Shutdown awaits promises returned by `'close'` listeners; earlier releases emitted `'close'` without waiting, so async cleanup could be cut off by the worker exiting
References
  1. In Markdown documentation, use hyphens ('-') instead of em dashes ('—') as field separators (e.g., separating configuration option names from their descriptions) to adhere to the style guide, even if the file has pre-existing inconsistencies using em dashes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not taking this one. The em-dash rule is being over-generalized here.

AGENTS.md says em dashes "are fine for parenthetical asides and match existing prose; do not use them as field separators." The field-separator prohibition refers to the bullet immediately above it - Type: / Default: reference blocks, where each field goes on its own line. It is not a ban on - **term** — description in prose lists.

Two checks: the docs tree has 633 em-dash list-item lines across 63 reference files, and AGENTS.md itself uses this exact pattern - including in the rule being quoted (- **Hyphens vs. dashes** — em dashes...).

So these are the established convention, not "pre-existing inconsistencies." Applying the suggestion would leave the touched lines inconsistent with every sibling line in the same list.

sent with Claude Opus 5

@github-actions
github-actions Bot temporarily deployed to pr-647 August 27, 2026 20:43 Inactive
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-647

This preview will update automatically when you push new commits.

Ethan-Arrowood and others added 2 commits August 27, 2026 15:36
The only coverage of shutdown cleanup was a single line in the Scope
events list ("Emitted after `scope.close()` is called"), which reads as
if the plugin author calls `scope.close()` themselves. Nothing stated
that Harper calls it during shutdown and graceful restart, which is the
reason an application would listen for the event at all.

- Add a "Cleanup on Shutdown" section to plugin-api.md with the
  `scope.once('close', ...)` pattern, covering the shutdown sequence:
  async listeners are awaited, cleanup is bounded by the termination
  backstop, a rejecting listener is logged and stops the wait on its
  siblings, and each worker cleans up independently.
- Clarify the `'close'` event and `scope.close()` entries in place and
  link them to the new section.
- Add a "Shutdown Cleanup" pointer to applications.md so the
  application-building audience finds it (that page had no occurrence of
  "cleanup", "shutdown", or "teardown").
- Record the v5.1.3 behavior change in the Version History list.

Verified against harper: componentLoader.ts:700 calls `scope.close()` on
the SHUTDOWN ITC message, restartWorkers posts the same message, and
Scope.ts:275-288 awaits promises returned by 'close' listeners.

Closes #604

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review feedback on #647: scope.close() awaits promises returned by
'close' listeners as of v5.1.3 (v5.1.2 emitted 'close' without
awaiting). Per CONTRIBUTING.md, behavior changes to existing surface
get a standalone <VersionBadge type="changed" /> below the heading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚀 Preview Deployment

Your preview deployment is ready!

🔗 Preview URL: https://preview.harper-documentation.harperfabric.com/pr-647

This preview will update automatically when you push new commits.

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.

Document app shutdown cleanup (scope 'close') — currently a bare event-list line in plugin-api.md

2 participants