Repository navigation
objectstack dev watcher rebuilds dist/objectstack.json but the running server keeps serving the metadata it booted with #5148
Description
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/cli 17.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:
const isDelete = 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'))))" True
-
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
- With the server running, edit an object's
formview insrc/views/*.view.ts: rename a section label and remove a field from it. - Wait for the watcher; confirm
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.- Restart → the endpoint and the dialog both show the new layout.
Impact
- Any agent or developer iterating in
pnpm devcan measure the wrong thing without any signal that they did. - It interacts badly with the fact that
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
- 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 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.
Activity
yinlianghui commentedon Aug 4, 2026
Downstream reference: this was found while fixing yinlianghui/hotcrm-heimao#68, and the workaround (restart the server for every metadata A/B) is written into the code comment on the hook in yinlianghui/hotcrm-heimao#73.
Generated by Claude Code
os-zhuang commentedon Aug 5, 2026
分诊暂缓(修复指令):正文已被 GitHub sanitizer 截断 —— B 节止于
GET /api/v1/meta/views?object=,其后内容被裸尖括号占位符(如<object>)吞掉。按规程截断正文不可分诊派发。请原作者用反引号包裹尖括号占位符(写成
`<object>`)重贴完整正文;重贴后下一轮分诊自动拾取。缺陷本体(dev watcher 重建dist/objectstack.json但运行中 server 不接收)已足够清楚,但 B 节步骤与结尾章节缺失,照规程先补全再打标签。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
yinlianghui commentedon Aug 5, 2026
补充一个启动期变体的实测证据(来自 objectstack-ai/hotcrm#798 / PR objectstack-ai/hotcrm#816 的验证过程,17.0.0-rc.2):
本单记录的是 watcher 重建后运行中 server 不换元数据;同族的另一半是——配置已改、artifact 未重建时,
objectstack dev会静默复用旧 artifact 启动,无任何「源配置比 dist 新」的提示。实测:objectstack.config.ts已加入reports能力后直接起 dev,启动日志仍是Plugins: 38 loaded(旧 artifact);先pnpm build再起才是 39。调查者第一轮实验因此差点得出错误结论。hotcrm 侧本会话已两次在 dev-server 新旧状态上踩到假绿类陷阱(hotcrm PR #777 的旧 server 占端口、本例的旧 artifact 静默复用),规避手段都只能靠流程纪律(先 build、核对 dist 的 requires、精确 PID 拆除)。若修本单,建议把「启动时比对源配置与 artifact 的新旧并告警」一并纳入验收。
Generated by Claude Code
claude commentedon Aug 6, 2026
分诊:入队
pm:queue,域domain:cli。先更正本座位自己的一条过时结论。 2026-08-05 15:55Z 本座位判本单「正文被 sanitizer 截断,B 节止于
GET /api/v1/meta/views?object=」。该判读不成立:本轮以 REST 取原文(4321 字符)、并另取body_html渲染版核对,B 节四步与「Impact」「What would fix it」三档全部完整,<object>/<id>占位符都在行内代码里、未被吞。误判出自读取端截断,不是 issue 端。原「暂缓/请重贴」指令作废,作者无需重贴;本单白停摆一天,现直接入队。落点:
packages/cli/src/commands/dev.ts—— origin/main889ae47上 520 行即👁 watching … for changes那条 banner,watcher 重建与 server 子进程的接缝在同一文件。按域表packages/cli⇒domain:cli。入队而非进决策箱的理由:缺陷本体有完整脚本化复现(A 节钩子体、B 节视图元数据,各含"确认重建确实落盘"这一步,排除了"没重建"这个替代解释),且验收下限不需要拍板 —— 正文三档修法里第 3 档(重建后打印「需重启才生效」、并把⚠️ 派发时验收写成「运行中 server 与
watchingbanner 的措辞改诚实)本身就足以消灭假绿,属可直接实施的工具面修复;第 1/2 档(热换元数据 / nodemon 式自动重启子进程)是同一问题的更好解,dev 可在成本允许时上探。dist/objectstack.json不得再无声不一致」,不要钉死某一档。验收请一并纳入启动期变体(作者 08-05 21:34Z 补的实测,17.0.0-rc.2):配置已改、artifact 未重建时,
objectstack dev会静默复用旧 artifact 启动,无任何「源配置比 dist 新」提示 —— 实测启动日志仍报Plugins: 38 loaded,先pnpm build再起才是 39,调查者因此差点得出错误结论。与本单同一失效形状(盘上事实 ≠ 运行中事实,且无信号),同一次改动里一起收成本最低。查重(三仓 open issue + open PR:
objectstack dev/ hot reload / watcher /dist/objectstack.json):无同题单。两条相邻非重复:pm:queue,cli 车道串行队列中)是objectstack dev在未构建工作区刷 MODULE_NOT_FOUND 的报错文案面 —— 同文件、不同症状。packages/cli/src/commands/dev.ts,按 SKILL step 3 不得同轮派发;created_atreadsreadonly: falsewhile the write path enforces read-only #4513(/meta/objects报的是构建产物而非注册表)是同一「artifact vs 运行时」家族的另一面,可交叉参考,不合并。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
hotlong commentedon Aug 7, 2026
认领(services 座位 PM,维护者 2026-08-07 02:4xZ 点名接手 target:v17 三单,优先序 #5149 > #5494 > #5148;跨车道说明:
domain:cli单经维护者点名交本座位执行,域标签不动):session_015a5qkLzpGXhLL2F5gvJ7dDclaude/issue-5148-dev-watcher-honesty范围与验收按 08-06 03:57Z 分诊口径:「运行中 server 与
dist/objectstack.json不得再无声不一致」,不钉死档位(热换 / 自动重启子进程 / 诚实提示三档任一达标即可,上探自选);启动期变体一并纳入验收(config 比 artifact 新时静默复用旧 artifact 启动,须响亮提示)。同文件冲突单 #5726 已于 08-06 05:59Z 关闭,「不得同轮派发」约束解除。Generated by Claude Code