HarperFast / HarperFast/harper
static assets serve the previous deployment's content after deploy_component(restart:false) until an explicit restart
- Dominant language
- JavaScript
- Stars
- 89
- Forks
- 10
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 200
Description
Static assets keep serving the **previous deployment's** file after `deploy_component(restart: false)`. The redeployed content only appears after an explicit restart.
Confirmed intent (Kris): static should serve redeployed content immediately — it has no cache or restart dependency, which is also how the `static` plugin is described relative to `jsResource` (which legitimately freezes its module until a restart).
## Measurements
Reproduced with a component declaring `static: { files: 'public/**', urlPath: '/qa763-static' }`, redeployed via `package_component` → `deploy_component(restart: false)` with the file's content changed `V1` → `V2`:
| where | static serves |
|---|---|
| `main` @ `eb702ee52` | **V1** (stale) |
| `5db825112` — before the entry-event contract change (#1936 / #1934 lineage) | **V1** (stale) |
| `main` + a `case 'change':` added to `server/static.ts` | **V1** (stale) |
The oracle is non-vacuous: the same run proves `V1` is being served on all three surfaces (jsResource, fastifyRoutes, static) before the redeploy, and the two non-static surfaces behave as #1934/#1936 intend.
## This is not a #1936 regression
I expected it to be one and it is not — it fails identically on both sides of that change.
#1936 restored the `scope.handleEntry()` contract (unchanged files silent, modified files emit `change`), which fixes `.env` / `fastifyRoutes` / `jsResource` staleness because all three use `if (entry.eventType !== 'add') scope.requestRestart()` — a `change` reaches them and flags a restart.
**Static is a different consumer and was never in scope for that fix.**
## A real but separate gap in `server/static.ts`
Its `handleEntry` callback (`server/static.ts:255`) switches on `eventType` with cases for `addDir`, `unlinkDir`, `add`, `unlink` — **no `change` case and no `default`**, so a modified file's event is silently dropped. `EntryHandler` does emit `change` (`components/EntryHandler.ts:329`), and no consumer outside `EntryHandler` itself handles it.
That looks wrong on its own and is worth fixing regardless. But **it is not the cause here** — adding the case does not change the result.
## What's eliminated, and where I'd look next
- **Not content caching.** The map stores `urlPath → absolutePath` and serves via `send`; the `// Store the file in memory for serving` comment above that line is misleading (the value is a path, not contents). So if the mapped path were current, disk content would be served fresh.
- It follows that **the mapped path is stale** — static keeps pointing at the previous deployment's file — but I have not confirmed why the new deployment's `add` doesn't replace it.
Leading untested hypothesis: `staticFiles` / `staticFileOwners` are closures inside `handleApplication(scope)`, so a redeploy that creates a **new scope** registers a second `scope.server.http` middleware while the old scope's middleware still matches first and answers from its stale map. The existence of an "owners" concept at all is consistent with multiple owners registering the same `urlPath`.
## Reproduction
`qa763-redeploy-env` from the exploratory-QA candidate set covers this; its two non-static arms pass on current main and only the static control fails. Happy to promote it as a regression anchor once this is fixed — it is otherwise ready.
Found during triage of QA candidates that were red on `main`.
Contributor guide
Research direction
Run the qa763-redeploy-env reproduction first, then read server/static.ts around handleEntry and handleApplication, along with components/EntryHandler.ts:329. Trace how redeployments register static files and middleware, using the staticFiles and staticFileOwners paths mentioned in the report. Done means a changed asset serves V2 immediately after deploy_component(restart: false), while the existing jsResource and fastifyRoutes checks still pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100