Skip to content

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

@hotlong

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)

time (UTC) event source
08:53:13 install written; last_published_at (freshness) stamped control plane sys_package_installation / sys_environment
08:53:25 [KernelManager] kernel demoted to stale — serving it while the rebuild runs {reason: "freshness"} staging container log
08:54:25 new kernel ready. From here GET /api/v1/meta/app returns HotCRM (crm_enterprise) container log
08:59:54 sample-data replay finished in the background (9 accounts etc.) log + sample_data_seeded_at
09:2xZ a fresh browser tab shows HotCRM first under 「我的应用」 (3 apps), with 18 crm_* objects reading

So 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 on main):

  • On a successful install it clears objectui:metadata:* from sessionStorage and calls refreshMetadata() immediately.
  • That refetch lands inside the stale-serve window above, so the fresh app list is the pre-install one.
  • MetadataProvider then 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.
  • The result: the console shows the success toast while the app never appears, until the cache is refetched or the tab is closed.

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:

  1. On a staging or local stack whose rebuild takes more than a few seconds, install an app from the env console's package page.
  2. Without closing the tab, open 「我的应用」 and the app switcher at +10 s, +90 s and after a same-tab reload.
  3. Compare with a new tab.

Fix direction (for the dev to measure)

  • Hold the refresh until the app is served. After a successful install into the current environment, poll 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.
  • Tell the user what is happening. Show a 「正在部署应用…」 state rather than declaring success while the app is absent. Once it appears, offer 「打开 HotCRM」.
  • Never persist an app list fetched during the window in which the install is known not to be served yet.
  • Alternative: the runtime exposes "rebuild pending" (the freshness state it already logs) so the console can wait on that instead of on the list.

Found by the maintainer's session 64cb68a6-01c7-4dc6-acc6-ccb459c90ef2.

Activity

  1. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Triage: lane added, bug · priority:p2 (kept) · domain:ui · area:studio, in pm:queue

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-10T09:52Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Lane: MarketplacePackagePage.tsx and MetadataProvider's cache are the console's, so domain: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/app serves 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 app list fetched while the install is known not to be served.
      • ⛔ No console-side guess at the rebuild duration.
    • 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.
  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 10, 2026
  3. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Claim: 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 in packages/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 neither MarketplacePackagePage.tsx nor MetadataProvider.tsx. objectui#12081's branch touches only a Home test named HomePage.marketplaceDisabled.test.tsx.

    Clause-② is declared yes because the deploying state's copy (「正在部署应用…」, 「打开 APP」) will add published locale keys. The in-seat review needs that reason.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    os-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

  5. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    ACCEPT — PR objectui#12096 (head c88aaf17b5) · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T17:45Z

    Reviewed 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, and Clause-②: yes sits at the start of a line.
      • No other card number sits near a closing keyword.
    • 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/app past the cache: at once, then every 5 s for up to 5 min, until an app's _packageId equals the package's manifest_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.
      • MetadataProvider is untouched.
    • Contract review:
      • Record 6098618574 on 8810eb0805 was FAIL on ② alone: @object-ui/i18n was patch while TranslationKeys widens.
      • REWORK 6098626260 sent a one-file patch round.
      • Record 6100405202 on c88aaf17b5 is PASS. The hop touched only the changeset (i18n is now minor, with a true closing sentence). The PR body carries the third narrowing, with the proxy-path qualification judged right.
    • Gates on the head:
      • 42 of 43 runs are green or skipped.
      • The one red is Spec Main Shape Gate (run 114237633531): objectui#12093's two signatures in four files outside this diff. The stand-down is 6098433785.
    • The record's ③ escalations, done by the seat:
    • 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 Gate is 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 merges main, a pure hop that needs no new record, and lands through the queue once green.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 10, 2026

    @objectstack-fleet
    Contributor

    Landed: PR objectui#12096 → main 3c0f8069 · domain:ui seat 3, session_01B1gHb9baeX7oioD5sHVm7z, 2026-10-10T20:09Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions