Skip to content

cli/daemon: run first-watch app listeners in the background - #2624

Draft
dancannon wants to merge 1 commit into
encoredev:mainfrom
dancannon:perf-listener
Draft

dancannon wants to merge 1 commit into
encoredev:mainfrom
dancannon:perf-listener

Conversation

@dancannon

@dancannon dancannon commented Oct 27, 2026 •

Copy link
Copy Markdown

encore run waits for the app listeners (regenerating the user-facing code, updating .gitignore) to finish before it starts serving. They re-parse the app, which is the second parse of a run, and nothing depends on it finishing, so the "server running" banner no longer needs to wait for it.

  • Run notifyAppListeners from Instance.Watch in a goroutine that recovers and logs panics.
  • Add a test that the first Watch returns while a listener is still blocked.

Why this should not change existing behaviour

  • The same listeners run with the same arguments on the same first-watch trigger. Only the caller stops waiting for them.
  • Nothing consumes their result. Watch returns only a subscription ID, and the run builds and starts the app from its own parse and overlays, not from the files the listeners write (the user-facing wrappers and .gitignore entries are for editors and tooling).
  • Listeners already run concurrently with reloads today. The file watcher calls regenerateUserCode from a debounced goroutine on every change, and the same function is exposed through the GenWrappers RPC. This change adds one more caller of that path and does not introduce concurrent use of it.
  • A panic in a listener is recovered and logged with its stack. Before, it propagated into the caller of Watch.
  • Watch has one caller, the run's file watcher, so there is no other code relying on the old ordering.

Known trade-off: the listeners cache the app's metadata after parsing it. A reload that lands while they are still running can have its metadata replaced by the older parse until the next parse corrects it. The cache is read by the dashboard and MCP tools, not by the build. The window is one parse long.

Testing

  • go test -race ./cli/daemon/apps/...
  • go vet, golangci-lint --new-from-rev=origin/main (0 issues) and a Windows build.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

When an app is first watched, its listeners (regenerating the user-facing
code, updating .gitignore) run inside Watch, so `encore run` waits for a
second parse of the app before it starts serving. Nothing depends on them
finishing, so run them in a goroutine that recovers and logs panics.

The listeners cache the app's metadata after parsing it. A reload that lands
while they are still running can have its metadata replaced by the older parse
until the next parse corrects it.

The "server running" banner no longer waits for that second parse.

This branch has not been deployed

No deployments
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