Summary
objectstack dev announces 👁 watching objectstack.config.ts, src for changes and does rebuild dist/objectstack.json when a source file changes. The running server does not pick the rebuild up. It keeps executing and serving the metadata it booted with, indefinitely, for both hook bodies and view metadata. Only a process restart applies a change.
There is no error, no warning, and no visible difference — the watcher's rebuild looks like a successful hot reload.
Why this is worse than "hot reload is not implemented"
The artifact on disk and the behaviour of the server disagree, silently. That makes every edit/verify loop in dev mode capable of producing a false result in either direction, and the failure is indistinguishable from the change itself being wrong:
- Fix something, save, re-test → still broken → conclude "my fix does not work" and go rewrite correct code.
- Break something, save, re-test → still fine → conclude "this code is not load-bearing" and delete it.
We hit the second one directly. A/B-ing two variants of one hook against a dev server without restarting compares one build against itself, and both variants "passed". The wrong conclusion was already written into a source comment before it was caught.
Reproduction (measured 2026-08-04, @objectstack/runtime / @objectstack/cli17.0.0-rc.1, Node v22.22.2, driver-sql/sqlite)
App: an object crm_customer_rating with an afterInsert/afterUpdate/afterDelete hook that clears three columns on a parent record when the last approved child is deleted.
A. Hook bodies do not reload
rm -rf .objectstack/data dist && pnpm dev (objectstack dev -p 4001 --compile). Wait for ready.
Confirm the behaviour: create a child, approve it, DELETE /api/v1/data/crm_customer_rating/<id> → parent's three columns clear. ✅
Without stopping the server, edit the hook body so the delete path is the first thing that returns:
constisDelete=event==='afterDelete';if(isDelete)return;// <- disable the whole delete path
Wait for the watcher. Confirm the rebuild really landed on disk:
$ python3 -c "import json;print('if (isDelete) return;' in json.dumps(json.load(open('dist/objectstack.json'))))"TrueRepeat step 2 against the still-running server.
Expected: the parent keeps its values (the delete path is disabled).
Actual: the parent's columns clear — i.e. the old hook body ran.
Restart the process (no other change). Repeat step 2 → the parent keeps its values. The new body is live only now.
B. View metadata does not reload either
- With the server running, edit an object's
form view in src/views/*.view.ts: rename a section label and remove a field from it. - Wait for the watcher; confirm
dist/objectstack.json contains the new label. GET /api/v1/meta/views?object=<object> → the oldconfig.sections comes back (old label, field still present), and the console's create dialog renders the old layout.- Restart → the endpoint and the dialog both show the new layout.
Impact
- Any agent or developer iterating in
pnpm dev can measure the wrong thing without any signal that they did. - It interacts badly with the fact that
dist/ is gitignored and objectstack dev only recompiles when dist/objectstack.json is absent: git pull does not touch dist/, clearing the database does not touch dist/, and now neither does saving a file while the server runs. The downstream symptom is always the same — "the code is new, the running app is old".
What would fix it, in rough order of preference
- Reload the artifact after a watcher rebuild — re-register hook bodies and view/object metadata into the live server.
- If a live swap is not safe for all metadata kinds, restart the child process automatically after a successful rebuild (nodemon-style), which is what the watcher already implies.
- At minimum, stop implying otherwise: log something like
rebuilt dist/objectstack.json — restart required for changes to take effect on every rebuild, and say so in the watching … banner. A truthful message is far better than a silent stale server.
Happy to test a fix against this app; the repro above is scripted on our side.
Summary
objectstack devannounces👁 watching objectstack.config.ts, src for changesand does rebuilddist/objectstack.jsonwhen a source file changes. The running server does not pick the rebuild up. It keeps executing and serving the metadata it booted with, indefinitely, for both hook bodies and view metadata. Only a process restart applies a change.There is no error, no warning, and no visible difference — the watcher's rebuild looks like a successful hot reload.
Why this is worse than "hot reload is not implemented"
The artifact on disk and the behaviour of the server disagree, silently. That makes every edit/verify loop in dev mode capable of producing a false result in either direction, and the failure is indistinguishable from the change itself being wrong:
We hit the second one directly. A/B-ing two variants of one hook against a dev server without restarting compares one build against itself, and both variants "passed". The wrong conclusion was already written into a source comment before it was caught.
Reproduction (measured 2026-08-04,
@objectstack/runtime/@objectstack/cli17.0.0-rc.1, Node v22.22.2, driver-sql/sqlite)App: an object
crm_customer_ratingwith anafterInsert/afterUpdate/afterDeletehook that clears three columns on a parent record when the last approved child is deleted.A. Hook bodies do not reload
rm -rf .objectstack/data dist && pnpm dev(objectstack dev -p 4001 --compile). Wait for ready.Confirm the behaviour: create a child, approve it,
DELETE /api/v1/data/crm_customer_rating/<id>→ parent's three columns clear. ✅Without stopping the server, edit the hook body so the delete path is the first thing that returns:
Wait for the watcher. Confirm the rebuild really landed on disk:
Repeat step 2 against the still-running server.
Expected: the parent keeps its values (the delete path is disabled).
Actual: the parent's columns clear — i.e. the old hook body ran.
Restart the process (no other change). Repeat step 2 → the parent keeps its values. The new body is live only now.
B. View metadata does not reload either
formview insrc/views/*.view.ts: rename a section label and remove a field from it.dist/objectstack.jsoncontains the new label.GET /api/v1/meta/views?object=<object>→ the oldconfig.sectionscomes back (old label, field still present), and the console's create dialog renders the old layout.Impact
pnpm devcan measure the wrong thing without any signal that they did.dist/is gitignored andobjectstack devonly recompiles whendist/objectstack.jsonis absent:git pulldoes not touchdist/, clearing the database does not touchdist/, and now neither does saving a file while the server runs. The downstream symptom is always the same — "the code is new, the running app is old".What would fix it, in rough order of preference
rebuilt dist/objectstack.json — restart required for changes to take effecton every rebuild, and say so in thewatching …banner. A truthful message is far better than a silent stale server.Happy to test a fix against this app; the repro above is scripted on our side.