Repository navigation
marketplace: after a successful install, the console refreshes the app list while the runtime still serves the pre-install kernel (stale-while-rebuild, ~60 s) and caches it — the installed app does not appear #12087
Description
Activity
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsTriage: lane added,
bug·priority:p2(kept) ·domain:ui·area:studio, inpm:queueTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-10T09:52Z. ⛔ Not a claim, ⛔ not a dispatch.- Lane:
MarketplacePackagePage.tsxandMetadataProvider's cache are the console's, sodomain:ui. - Why p2, as filed: an install that succeeded looks failed in the same tab until the cache is refetched or the tab closes.
- Direction, the card's own first two items:
- After a successful install into the current environment, wait until
GET /meta/appserves the installed package's app. Poll on a bound, with no fixed sleep. Only then refresh and persist the metadata cache. - Show 「正在部署应用…」 until then, and offer 「打开 」 when it appears.
- ⛔ Never persist an
applist fetched while the install is known not to be served. - ⛔ No console-side guess at the rebuild duration.
- After a successful install into the current environment, wait until
- The alternative (the runtime exposes "rebuild pending") is a server surface in objectstack or cloud. It is only worth filing if the bounded poll cannot tell "still rebuilding" from "install did not take". The claimant states which it measured.
- Measure first: the repro in the card, on a stack whose rebuild takes more than a few seconds.
- Lane:
- addedarea:studioChanging a running app without code — authoring, publish, docs and the portalChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Oct 10, 2026 objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsClaim: PM loop round 1
Session:session_01B1gHb9baeX7oioD5sHVm7z
Account:marchtian
Branch:claude/issue-12087-install-wait-served
Worktree:objectui-issue-12087
Domain:domain:ui
Seat:domain:ui#3
File surface:packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx(the post-install refresh, about:337–:440);packages/app-shell/src/providers/MetadataProvider.tsx(only the cache write, if the "never persist a list fetched in the stale window" rule must live there); their tests; locale entries inpackages/i18n/src/locales/for the deploying state (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"
Clause-②: yes
Responsibility:objectui's app-shell MarketplacePackagePage.tsx, which refreshes and persists the app list immediately after a successful install, inside the runtime's stale-while-rebuild window | none: the runtime exposes no "rebuild pending" signal today (card body), and the bounded poll is the console-side path | every environment administrator who installs a package from the console marketplace; measured by the maintainer on staging on 2026-10-10
Thread-read: 6096303824
Serial constraints cleared: none. The in-flight branches of objectui#12079, #12081, #12082 and #12086 (diff file lists read at this claim) touch neitherMarketplacePackagePage.tsxnorMetadataProvider.tsx. objectui#12081's branch touches only a Home test namedHomePage.marketplaceDisabled.test.tsx.Clause-② is declared
yesbecause the deploying state's copy (「正在部署应用…」, 「打开 APP」) will add published locale keys. The in-seat review needs that reason.
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsos-dev-report
{ "issue": 12087, "status": "done", "branch": "claude/issue-12087-install-wait-served", "pr": "https://github.com/objectstack-ai/objectui/pull/12096", "session": "session_01B1gHb9baeX7oioD5sHVm7z (subagent mode: the parent seat's id)", "premise_still_valid": true, "summary": "After a cloud install into the environment this console renders, MarketplacePackagePage no longer refreshes in the stale-while-rebuild window. It reads GET /meta/app through the SDK client, past the metadata cache: at once, then every SERVED_APP_POLL_INTERVAL_MS (5 s) up to SERVED_APP_WAIT_CAP_MS (5 min), until an app's _packageId equals the package's manifest_id. Only then does it drop the objectui:metadata:* seed, run refreshMetadata() and emit the bus pulse; the pulse was a second in-window persist. Until then it shows 「正在部署应用…」 (dialog, then the page once the dialog is closed). Once served it offers 「打开 {name}」; on expiry it gives an honest timeout with 'Check again', and refreshes nothing. MetadataProvider is untouched. The wait is not cancelled on unmount. Suggested bindings mount only once the app is served. A cross-environment install keeps the old success text but no longer refreshes this console. Five locale keys are added in all ten packs. Measured by code read: the install-local and uninstall-local refresh sites have no rebuild window (the producer hot-registers or withdraws before answering), so they are unchanged. The console cannot tell 'still rebuilding' from 'did not take', from 'no app', from 'withheld', or an upgrade's old app from its new one; that condition triggers the server finding below. Live repro NOT MEASURED: there is no stale-while-rebuild stack in this container (the KernelManager lives in cloud), and staging needs the maintainer's session.", "tests": "All at 8810eb080, through os-verify-lock. (1) pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/console/marketplace/ packages/i18n/ -> 'Test Files 108 passed (108)', 'Tests 1488 passed | 13 skipped (1501)'; this includes the new waitForServedApp-12087.test.ts (6 cases, virtual clock) and MarketplacePackagePage.waitServedApp-12087.test.tsx (3 cases: real MetadataProvider and real assistant bus; every app-seed sessionStorage write recorded; stale window / expiry + Check again / CONTROL served at once with no sleep). (2) Ablation via objectstack scripts/ablation-replace.mjs, wrap mode, putting back the pre-fix in-window refreshMetadata() + emitMetadataRefresh() at the start of the wait: 'ok mutation landed: anchor 1 -> 0, blob d05b45979736 -> 2c6c92d29fea', MARKER=1 -> 'Tests 2 failed | 1 passed (3)'. Both failures are 'expected [ { afterInstall: true, …(1) }, …(1) ] to deeply equal []' (the persisted stale seed); the CONTROL stays green as predicted. Restore: 'ok restored: blob == HEAD (d05b45979736) and git diff HEAD is empty', POST_RESTORE_MARKER=0. The first ablation attempt was a no-op: its replacement contained the anchor, the tool refused before any run, and it is not counted. The tests import the page by relative path (src), so no dist preflight applies. (3) pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2 -> 'Tasks: 31 successful, 31 total'; both type-checks were cache misses and executed. tsc -p tsconfig.test.json --listFilesOnly lists both new test files.", "gates": [ { "cmd": "pnpm turbo run type-check --filter=@object-ui/app-shell --filter=@object-ui/i18n --concurrency=2", "exit": 0, "head": "8810eb080", "verdict": "Tasks: 31 successful, 31 total" }, { "cmd": "pnpm exec vitest run --maxWorkers=2 packages/app-shell/src/console/marketplace/ packages/i18n/", "exit": 0, "head": "8810eb080", "verdict": "Test Files 108 passed (108); Tests 1488 passed | 13 skipped (1501)" }, { "cmd": "pnpm check:i18n-keys", "exit": 0, "head": "8810eb080", "verdict": "Every in-scope call-site key resolves against the en pack (3319 keys)" }, { "cmd": "pnpm check:i18n-drift", "exit": 0, "head": "8810eb080", "verdict": "0 en value(s) changed (5 key(s) added, 0 removed)" }, { "cmd": "pnpm check:i18n-dead-keys", "exit": 0, "head": "8810eb080", "verdict": "report-only; no marketplace.install key among the candidates" }, { "cmd": "pnpm check:new-line-citations", "exit": 0, "head": "8810eb080", "verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0" }, { "cmd": "pnpm check:control-bytes", "exit": 0, "head": "8810eb080", "verdict": "check-control-bytes: OK (scanned 8291 tracked text file(s); skipped 85 binary)" }, { "cmd": "pnpm turbo run build --filter=@object-ui/console --concurrency=2 && pnpm check:eager-closure", "exit": 0, "head": "8810eb080", "verdict": "Console eager closure is 3169.7 KB gzipped across 290 of 2474 chunks (budget: 3204.6 KB, headroom: 34.8 KB); delta vs base de302c73 = +125 B gzipped total (i18n-locale-en +136 B, others +/-7 B hash churn), eager chunk count 290 -> 290; base read by restoring the branch's sources to de302c73 + vite build" }, { "cmd": "pnpm check:eager-locale-catalogues", "exit": 0, "head": "8810eb080", "verdict": "exit 0" }, { "cmd": "console build declared-lazy-views guard (inside vite build)", "exit": 0, "head": "8810eb080", "verdict": "passes at 8810eb080; at 78a7a9210 it refused (MarketplacePackagePage pulled eager by MetadataProvider co-tenancy via an extractItems value import), fixed in 8810eb080" }, { "cmd": "eslint --no-inline-config on the 17 touched files", "exit": 0, "head": "8810eb080", "verdict": "0 errors, 11 warnings, all of them already present (the same any-pattern count as the base page, plus its unused 'code')" }, { "cmd": "node scripts/check-changeset-presence.mjs; pnpm changeset:check; check-changeset-claims; check-changeset-overwrite; pnpm check:pending-changeset-literals", "exit": 0, "head": "8810eb080 (presence) / 78a7a9210 (the rest; changeset unchanged since)", "verdict": "all exit 0" }, { "cmd": "check-vi-mock-specifiers / check-vi-mock-override-shape / check-vi-mock-inherit / check-test-path-roots / check:unreferenced-sources / check:side-effects-array / check:esm-specifiers / check:phantom-deps / check-type-check-coverage / check-lint-coverage", "exit": 0, "head": "78a7a9210 (page-only change since: readApps)", "verdict": "all exit 0" }, { "cmd": "pnpm check:readme-exports", "exit": 1, "head": "78a7a9210", "verdict": "NOT MEASURED: PREREQUISITE NOT MET (cli and plugin-ai dist not built in this worktree); no README or export in this diff" } ], "line_budget": "n/a: no skills/** or line-ledgered file touched; diff 15 files, +749/-36 at 8810eb080 vs de302c73", "files_changed": [ ".changeset/12087-install-wait-served.md", "packages/app-shell/src/console/marketplace/MarketplacePackagePage.tsx", "packages/app-shell/src/console/marketplace/waitForServedApp.ts", "packages/app-shell/src/console/marketplace/__tests__/waitForServedApp-12087.test.ts", "packages/app-shell/src/console/marketplace/__tests__/MarketplacePackagePage.waitServedApp-12087.test.tsx", "packages/i18n/src/locales/ar.ts", "packages/i18n/src/locales/de.ts", "packages/i18n/src/locales/en.ts", "packages/i18n/src/locales/es.ts", "packages/i18n/src/locales/fr.ts", "packages/i18n/src/locales/ja.ts", "packages/i18n/src/locales/ko.ts", "packages/i18n/src/locales/pt.ts", "packages/i18n/src/locales/ru.ts", "packages/i18n/src/locales/zh.ts" ], "deviations": [ "New file packages/app-shell/src/console/marketplace/waitForServedApp.ts, outside the claim's listed file surface. It is a new sibling module that no other claim can hold. It keeps the helper exportable without a react-refresh only-export-components warning, and it lets the page test shrink the bound. The claim surface is otherwise kept: MetadataProvider.tsx is untouched and MarketplacePage.tsx is untouched.", "Two behaviour changes beyond the refresh, both in the same file. (a) SuggestedBindingsPanel mounts once the app is served instead of on install success; it reads the runtime once, on mount. (b) A cross-environment install no longer refreshes this console's own metadata or pulses the bus; the old comment called that a harmless no-op.", "Resumed after the 429 kill: the branch was fast-forwarded from 4d0ff233 to origin/main de302c73 before any code (BASE de302c73); pnpm install was re-run in the foreground (exit 0).", "Clause-② yes is confirmed by the diff: five new published keys under marketplace.install in all ten packs; no en value changed." ], "mcp_calls": "0", "api_writes": "3 — all REST writes through the fleet-write relay (each one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create POST /repos/objectstack-ai/objectui/pulls (draft) -> #12096, read-back 11512 bytes sent = 11512 stored; (2) label-write --assign marchtian -> POST /repos/objectstack-ai/objectui/issues/12096/assignees, read-back MATCHES; (3) this os-dev-report comment POST /repos/objectstack-ai/objectui/issues/12087/comments. git push x3 is not a REST write.", "cleanup": "worktree /home/user/objectui-issue-12087: node_modules removed and git worktree remove succeeded without --force; no background process of mine alive; remote branch = 8810eb0805da410c54ebdb76a207947c74f7aab3", "open_questions": [], "out_of_scope_findings": [ "class: triage-conditional server surface (triage 6096303824: 'only worth filing if the bounded poll cannot tell \"still rebuilding\" from \"install did not take\"'), not a/b/c · reach: not measured live (no stale-while-rebuild stack here); condition established by code read of objectstack 86da1949 · evidence: the console has no read that tells them apart. /meta/app lacks the app in both cases. /cloud-connection/installation answers installed:true in both (cloud-connection-plugin.ts, GET /installation). GET /packages/:id would 404 on the stale kernel and after a failed load alike. filterAppForUser withholds apps by requiredPermissions/requiresService with the same absence. For an UPGRADE, objectql registry.registerItem calls applyProtection(item, { packageId }) only, so served apps carry _packageId but no _packageVersion, and the stale kernel's old app satisfies any /meta/app predicate. The install answer (installation.{id, environment_id, package_id, version}) carries no rebuild state. A runtime 'rebuild pending / kernel generation' read (the KernelManager freshness state the card's log shows; it lives in cloud) would close all of these · dedupe words: rebuild pending, stale-while-rebuild, KernelManager freshness, kernel generation, _packageVersion registerItem", "carrier: the seat — a follow-up / sub-issue of objectui#12087 (or a claim-surface amendment) · noted in the PR's Acceptance notes, not filed · MarketplacePage doOrgInstall, cloud-managed branch: installPackage into the current environment then emitMetadataRefresh() at once, the same in-window persist. This is a code read, not reproduced. waitForServedApp takes pkg.manifest_id unchanged · dedupe words: org install stale app list, doOrgInstall emitMetadataRefresh", "carrier: 承接者:无 · noted in the PR's Acceptance notes, not filed · objectstack cloud-connection marketplace-install-local-plugin answers hotLoaded:true even on its lenient path where manifestService.register failed ('will load on next restart'), so the console's localSuccess ('should now appear') shows over a list without the app. This is a code read, not reproduced · dedupe words: install-local hotLoaded unconditional, hot-register failed lenient" ] }
Generated by Claude Code
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsACCEPT — PR objectui#12096 (head
c88aaf17b5) ·domain:uiseat 3,session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T17:45ZReviewed against GitHub and the diff, not the reports: round 1
6098339059, plus the patch round delivered to the seat.- Shape:
- The PR is a draft on
main. - Its first line is
Fixes #12087, andClause-②: yessits at the start of a line. - No other card number sits near a closing keyword.
- The PR is a draft on
- Scope:
- 15 files, +749/−36.
- Nothing touches
content/docs/releases/. NOT GOVERNED. - The one file beyond the claim's surface,
waitForServedApp.ts, is a new sibling module that no other claim holds.
- The code against the card:
- After an install into this environment, the package page reads
GET /meta/apppast the cache: at once, then every 5 s for up to 5 min, until an app's_packageIdequals the package'smanifest_id. - Only then does it drop the seed, refresh and pulse the bus.
- On expiry it says the install was recorded and the app is not served yet, offers "Check again", and refreshes nothing.
MetadataProvideris untouched.
- After an install into this environment, the package page reads
- Contract review:
- Record
6098618574on8810eb0805was FAIL on ② alone:@object-ui/i18nwaspatchwhileTranslationKeyswidens. - REWORK
6098626260sent a one-file patch round. - Record
6100405202onc88aaf17b5is PASS. The hop touched only the changeset (i18n is nowminor, with a true closing sentence). The PR body carries the third narrowing, with the proxy-path qualification judged right.
- Record
- Gates on the head:
- 42 of 43 runs are green or skipped.
- The one red is
Spec Main Shape Gate(run114237633531): objectui#12093's two signatures in four files outside this diff. The stand-down is6098433785.
- The record's ③ escalations, done by the seat:
- The server-surface finding (no rebuild-pending read; an upgrade's old app satisfies the predicate): registry: an app registered through
registerItemis served without the declared_packageVersion(the artifact loader path stamps it), so a console cannot tell an upgrade's old app from its new one objectstack#22689. - The sibling defect in
MarketplacePage.doOrgInstall: objectui#12097, which depends on this PR. install-localanswershotLoaded: trueon its lenient path: install-local: a cloud install whose hot-register fails answers 200 withhotLoaded: true(the lenient path), so a console says the app "should now appear" over a kernel that does not have it objectstack#22695.- Live repro NOT MEASURED: there is no stale-while-rebuild stack in a container. The ask goes to the maintainer: one staging install after landing (the card's repro at +10 s, +90 s and after a same-tab reload), with the reading recorded here.
- The server-surface finding (no rebuild-pending read; an upgrade's old app satisfies the predicate): registry: an app registered through
- Out of scope, noted, not filed: a remounted page shows no deploying state while the detached wait runs, so a second install starts a second bounded wait. It is cosmetic.
Landing: when every check on the head is green.
Spec Main Shape Gateis red for every objectui PR until objectui#12093's fix lands (folded into objectui#12085). Until then this PR stays a draft. After that it mergesmain, a pure hop that needs no new record, and lands through the queue once green.
Generated by Claude Code
- Shape:
objectstack-fleet commented
on Oct 10, 2026 ContributorMore actionsLanded: PR objectui#12096 →
main3c0f8069·domain:uiseat 3,session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T20:09Z- The merge: through the merge queue. Its one parent is
417e2456(objectui#12091 landed just before it in the same queue). - Content check:
- Five of the 15 changed files are blob-identical to the landing head
5b11c46e70:MarketplacePackagePage.tsx,waitForServedApp.ts, the two pin files, and the changeset. - The ten locale packs differ from the landing head only because objectui#12091 added its own
empty.*keys to the same packs one commit earlier. Their merge diff is patch-equivalent to this PR's own diff (git patch-id --stable3f16e878on both). - All five
marketplace.installkeys are present in every pack at3c0f8069.
- Five of the 15 changed files are blob-identical to the landing head
- Landing-head record:
6101529116is PASS on a pure base merge of the ACCEPTed head. - The card: closed
completedby the PR'sFixesline.pm:dispatchedcomes off in the label write after this comment. - ACCEPT
6100411973. - Still owed, not a landing condition: one staging install by the maintainer, with the card's repro read at +10 s, +90 s and after a same-tab reload, and the reading recorded here. Follow-ups already filed: registry: an app registered through
registerItemis served without the declared_packageVersion(the artifact loader path stamps it), so a console cannot tell an upgrade's old app from its new one objectstack#22689, objectui#12097, install-local: a cloud install whose hot-register fails answers 200 withhotLoaded: true(the lenient path), so a console says the app "should now appear" over a kernel that does not have it objectstack#22695.
Generated by Claude Code
- The merge: through the merge queue. Its one parent is
What the user saw
The maintainer installed HotCRM 3.1.0 from the environment console's marketplace page (staging
os-6hf1jq.objectos.app,/_console/apps/setup/system/marketplace/pkg_7d6823ee-…) on 2026-10-10 at 08:53Z. About 27 minutes later they reported 「我刚在环境中安装了 hotcrm,但是没显示出来」: HotCRM did not show among their apps.What was true server-side (measured)
last_published_at(freshness) stampedsys_package_installation/sys_environment[KernelManager] kernel demoted to stale — serving it while the rebuild runs {reason: "freshness"}GET /api/v1/meta/appreturns HotCRM (crm_enterprise)sample_data_seeded_atcrm_*objectsSo the install worked. The runtime served the pre-install kernel for ~60 s after the install answered, by design: stale-while-rebuild.
Why the console most likely kept showing the old list
MarketplacePackagePage.tsx(~:337–365 onmain):objectui:metadata:*from sessionStorage and callsrefreshMetadata()immediately.applist is the pre-install one.MetadataProviderthen caches that list: in memory with a 5-min TTL, and in the tab's sessionStorage seed (objectui:metadata:app:<org>:<principal>), which a reload in the same tab reads back first.This is inferred from the timeline and the code. It was not reproduced end-to-end in the maintainer's own tab.
Repro for the dev:
Fix direction (for the dev to measure)
GET /meta/app(bounded, e.g. every 5 s for up to 3–5 min) until the installed package's app appears, and only then refresh and persist the metadata cache.applist fetched during the window in which the install is known not to be served yet.Found by the maintainer's session
64cb68a6-01c7-4dc6-acc6-ccb459c90ef2.