You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
install-local: uninstalling a package (DELETE /api/v1/marketplace/install-local/ID) leaves its flows registered and triggerable in the automation engine until an unrelated metadata reload #27395
QA-source: #27392 · automation.packaged-flow-subflow-disable-refusal · observed beside the clauses
What happens
After an install-local uninstall, the package's flows are gone from the metadata registry, and GET /api/v1/meta/flow/NAME answers 404. The automation engine still holds them, though:
GET /api/v1/automation/NAME answers 200 with status: active and the uninstalled package's _packageId.
POST /api/v1/automation/NAME/trigger dispatches a run.
The flows stay live, along with any trigger they arm, until something else emits a metadata reload; a view publish was enough to clear them in this run. The uninstall door's own note says the package was "withdrawn from the running kernel", which the engine contradicts.
Reproduction (fresh showcase boot from a scratch copy of the app, admin bearer)
Use a scratch copy because install-local writes under the server's working directory.
POST /api/v1/marketplace/install-local with an inline manifest that ships one flow, for example qasub_second_notify_caller (the item's qa-second-packaged-caller recipe) → 200 hotLoaded: true.
GET /api/v1/automation/qasub_second_notify_caller → 200 with _provenance: package and _packageId: com.objectstack.qa.qasub.
DELETE /api/v1/marketplace/install-local/com.objectstack.qa.qasub → 200. The note reads "…the package withdrawn from the running kernel…", and the boot log shows [Registry] Unregistered flow … (package … uninstalled).
GET /api/v1/meta/flow/qasub_second_notify_caller → 404.
GET /api/v1/automation/qasub_second_notify_caller and POST /api/v1/automation/qasub_second_notify_caller/trigger with {}.
Expected: both answer 404, because the engine withdraws the uninstalled flow, as it does after the next metadata reload.
Actual: the GET answers 200 with status: active and the old _packageId, and the trigger writes a run row and dispatches the flow.
This was reproduced twice at c4c515ad, with two different packages and flows (qasub / qasub_second_notify_caller, and qasub2 / qasubb_probe_caller).
The subflow-disable guard itself is correct here: after the uninstall it no longer counts the withdrawn caller.
Mechanism
The uninstall route (packages/cloud-connection/src/marketplace-install-local-plugin.ts, the DELETE handler around L2243–2300) unregisters the package from the metadata registry. It never announces metadata:reloaded.
service-automation withdraws flows only from its re-sync on that event (packages/services/service-automation/src/plugin.ts, around L2546). So nothing tells the engine that the flows are gone.
Where a fix would go
The uninstall door should emit the same reload or withdraw signal the install door's hot-load path relies on, or call the automation service's withdrawal for the package's flows directly. The package's trigger bindings should be unbound in the same step.
Read fresh. After an install-local uninstall, the package's flows are gone from the registry (404), but the automation engine still holds them as active and /trigger still dispatches a run. That lasts until an unrelated metadata reload. The uninstall door says the package was "withdrawn from the running kernel", which is false.
p1: uninstall is a checklist capability, and the broken half has side effects: a withdrawn package's automation still writes.
⛔ The uninstall door drives the same unregister path a reload uses. It is one teardown, not a second engine-side list.
It is extracted from the 18.3.0 RC run, so it is listed in wave anchor [sweep] v18.3.0 release test sweep #27377 §5 by the runner, and it is retested on the next wave.
Claim: PM loop round 3 · card thread · p1 · 2026-10-29T10:05Z
Session: session_01L6bhgXcN9HaZYSEUdJWEjw (the seat); the card thread is session_01L6bhgTsswEK7wz8UFCtF5z
Account: os-sales (the seat's linked user; the card's assignee)
Branch: claude/project-thread-30b1mx (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: triage 6489212277, verbatim: "The uninstall door drives the same unregister path a reload uses. It is one teardown, not a second engine-side list."
File surface: packages/cloud-connection/src/marketplace-install-local-plugin.ts (the DELETE install-local handler only), and packages/services/service-automation/src/plugin.ts only if the measured withdrawal path needs it. Their tests, and one changeset. ⛔ No packages/spec/**; stop on breach and explain in the report.
Container & model: M, mode:thread, model: default tier, effort: xhigh (p1)
Clause-②: no
Metadata-protocol: none
Responsibility: the install-local uninstall door (DELETE /api/v1/marketplace/install-local/ID) leaves the package's flows registered and active in the automation engine, so /trigger still dispatches a run until an unrelated metadata reload | a withdrawn package's automation still writes, and the door's "withdrawn from the running kernel" note is false | any environment that uninstalls a locally installed package that ships flows
Thread-read: 6489212277
Premise: holds. Read at origin/mainf57eddcf0: the DELETE handler withdraws from the registry and runs the cleanups but never announces metadata:reloaded, while the install door does (around L3469–3510). service-automation withdraws flows only on that re-sync.
Serial constraints cleared: open PR #27344 (os-bill, #27237, credential refs) also edits marketplace-install-local-plugin.ts. The thread stays out of its hunks, merges main before pr_create, and runs git merge-tree against it. Whichever lands later merges main.
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
os-dev-report
{
"issue": 27395,
"status": "done",
"branch": "claude/project-thread-30b1mx",
"pr": "#27530",
"session": "session_01L6bhgTsswEK7wz8UFCtF5z",
"premise_still_valid": true,
"summary": "Premise held as dispatched: the install-local DELETE door withdrew the package from the registry and ran the uninstall cleanups but announced nothing, and service-automation withdraws a flow (deactivateFlowTrigger first) only in resyncFlowsFromProtocol on metadata:reloaded. handleUninstall now announces metadata:reloaded LAST, after the registry withdrawal and the cleanups, with the install door's payload { changed: ['app/ID'] }; it is skipped when the registry refused the withdrawal. A throwing subscriber or a context without trigger becomes one failed outcome named metadata:reloaded on cleanups (cause and restart remedy to the warn log only), and the note is composed per outcome, so the door's 'withdrawn from the running kernel' claim is now true. No engine-side list, no service-automation or packages/spec change; one patch changeset for @objectstack/cloud-connection.",
"tests": "Red first (243399d, base blob of the plugin written over the fix, restore proved by hash-object == HEAD blob and clean status): unit src/marketplace-install-local-uninstall-resync.test.ts 3 failed / 4 passed (the 4 announce-nothing controls pass on base); dogfood test/install-local-uninstall-flows-withdrawn.dogfood.test.ts after rebuilding cloud-connection from base (preflight announceUninstall --absent in all 6 built files) 3 failed / 2 passed with the QA readings: GET /automation/qauninst_manual_probe 200 status active _packageId com.example.qauninst, trigger 200 run completed, record flow listed 2 / rows 2 expected 1 / 1. Green on fix: unit 7/7, dogfood 5/5. Ablation via scripts/ablation-replace.mjs (fix committed first): anchor const resync = withdrawal ? undefined : await this.announceUninstall(ctx, manifestId); hit once, blob e25af962d8ba to 3bdeeea26dea, build exit 0, preflight marker ablated27395 in 2 built files: dogfood 3 failed / 2 passed, unit 3 failed / 4 passed; restore blob == HEAD, git diff HEAD empty, rebuild + preflight --absent in 6 files, dogfood 5/5. A first mutate leg without the void read is not counted (DTS build exit 1, TS6133). Moved pins: 3 existing uninstall rigs gained a no-op async trigger on their ctx (rig only, no assertion changed); without it 12 tests failed on the new failed outcome. On merged head 7cb1ca3 (main ded67bf merged, closure rebuilt VERDICT command-exit 0, 67/67): pnpm --filter @objectstack/cloud-connection test VERDICT command-exit 0, Test Files 60 passed (60), Tests 754 passed (754); typecheck VERDICT command-exit 0; the dogfood file VERDICT command-exit 0, 5 passed (5).",
"gates": "Local subset on 7cb1ca3: node scripts/pm/dispatch-gates.mjs --commands derived 64 commands; all 64 run with exit captured before any pipe, 64 exit 0. pnpm check:dual-build-cjs-loads first answered exit 3 (PREREQUISITE NOT MET: 7 packages outside the closure had no dist); built them (turbo 43/43, VERDICT command-exit 0) and it exited 0: 112 require entry points across 69 packages load. check:type-check-debt VERDICT command-exit 0, 0 raw tsc errors above ledger. dispatch-gates --ran: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, a DERIVED zero (all 64 carry an exit code, none is 3). test:repo not run locally (no repo-project test edited, not derived); CI families: PR 27530 check-runs pending at report time, to be read by the seat at ACCEPT.",
"dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
"mcp_calls": "2 — mcp__github__issue_read x2 (card and triage reads), read-only; plus mcp__claude-code-remote__subscribe_pr_activity on PR 27530 (harness subscription, not a GitHub write). Zero GitHub MCP write tools.",
"api_writes": "2 — (1) relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches, run 40861851173 success, POST /pulls draft = PR 27530, body read back 13740 bytes sent / 13740 stored identical; (2) this report: scripts/pm/post-stamped.mjs over the relay transport (POST /repos/objectstack-ai/objectstack/dispatches, landing as an issue comment on 27395). git push is not a REST write.",
"open_questions": [],
"deviations": [
"The dispatch asked for pr_create with assignees os-sales and pr_request_reviewers os-sales. Not done: pm-dispatch SKILL.md says pr_create 不请不派 and ⛔ 永不派/请席位自身账号 (the review request is the seat's hand-off after ACCEPT + same-head PASS), and os-dev.md says a dev's PR carries no assignees. The PR opened unassigned with no review request; the seat can do the hand-off itself.",
"PR 27344 overlap: its head 355edc0 re-read unchanged before pr_create; bare-clone git merge-tree --write-tree of this head against it exits 0 (tree 674cf2d5f8); hunks disjoint (theirs 214/734/1954/1969/3464, ours 111/303/2291-2308)."
],
"out_of_scope_findings": [
"class: none (read-only inference, not filed) · reach: not measured · the runtime's DELETE /api/v1/packages/:id (packages/runtime/src/domains/packages.ts) withdraws via deletePackage / uninstallPackage and also announces no metadata:reloaded; whether that door leaves flows live depends on what deletePackage's stored-row deletes signal to the engine · dedupe words: packages DELETE flows still active, uninstall metadata:reloaded runtime, deletePackage automation resync · carrier: none · noted in PR 27530 Acceptance notes"
]
}
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 发布并装进一个环境 | platform-core.marketplace-install-local-lifecycle | P④
Triage: p1 ·
domain:server·area:automation· 2026-10-29T05:57Z⛔ Not a claim.
activeand/triggerstill dispatches a run. That lasts until an unrelated metadata reload. The uninstall door says the package was "withdrawn from the running kernel", which is false.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
Claim: PM loop round 3 · card thread · p1 · 2026-10-29T10:05Z
Session:
session_01L6bhgXcN9HaZYSEUdJWEjw(the seat); the card thread issession_01L6bhgTsswEK7wz8UFCtF5zAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/project-thread-30b1mx(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage 6489212277, verbatim: "The uninstall door drives the same unregister path a reload uses. It is one teardown, not a second engine-side list."
File surface:
packages/cloud-connection/src/marketplace-install-local-plugin.ts(the DELETE install-local handler only), andpackages/services/service-automation/src/plugin.tsonly if the measured withdrawal path needs it. Their tests, and one changeset. ⛔ Nopackages/spec/**; stop on breach and explain in the report.Container & model:
M,mode:thread,model: default tier,effort: xhigh(p1)Clause-②: no
Metadata-protocol: none
Responsibility: the install-local uninstall door (
DELETE /api/v1/marketplace/install-local/ID) leaves the package's flows registered andactivein the automation engine, so/triggerstill dispatches a run until an unrelated metadata reload | a withdrawn package's automation still writes, and the door's "withdrawn from the running kernel" note is false | any environment that uninstalls a locally installed package that ships flowsThread-read: 6489212277
Premise: holds. Read at
origin/mainf57eddcf0: the DELETE handler withdraws from the registry and runs the cleanups but never announcesmetadata:reloaded, while the install door does (around L3469–3510). service-automation withdraws flows only on that re-sync.Serial constraints cleared: open PR #27344 (
os-bill, #27237, credential refs) also editsmarketplace-install-local-plugin.ts. The thread stays out of its hunks, mergesmainbeforepr_create, and runsgit merge-treeagainst it. Whichever lands later mergesmain.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
os-dev-report
{
"issue": 27395,
"status": "done",
"branch": "claude/project-thread-30b1mx",
"pr": "#27530",
"session": "session_01L6bhgTsswEK7wz8UFCtF5z",
"premise_still_valid": true,
"summary": "Premise held as dispatched: the install-local DELETE door withdrew the package from the registry and ran the uninstall cleanups but announced nothing, and service-automation withdraws a flow (deactivateFlowTrigger first) only in resyncFlowsFromProtocol on metadata:reloaded. handleUninstall now announces metadata:reloaded LAST, after the registry withdrawal and the cleanups, with the install door's payload { changed: ['app/ID'] }; it is skipped when the registry refused the withdrawal. A throwing subscriber or a context without trigger becomes one failed outcome named metadata:reloaded on cleanups (cause and restart remedy to the warn log only), and the note is composed per outcome, so the door's 'withdrawn from the running kernel' claim is now true. No engine-side list, no service-automation or packages/spec change; one patch changeset for @objectstack/cloud-connection.",
"tests": "Red first (243399d, base blob of the plugin written over the fix, restore proved by hash-object == HEAD blob and clean status): unit src/marketplace-install-local-uninstall-resync.test.ts 3 failed / 4 passed (the 4 announce-nothing controls pass on base); dogfood test/install-local-uninstall-flows-withdrawn.dogfood.test.ts after rebuilding cloud-connection from base (preflight announceUninstall --absent in all 6 built files) 3 failed / 2 passed with the QA readings: GET /automation/qauninst_manual_probe 200 status active _packageId com.example.qauninst, trigger 200 run completed, record flow listed 2 / rows 2 expected 1 / 1. Green on fix: unit 7/7, dogfood 5/5. Ablation via scripts/ablation-replace.mjs (fix committed first): anchor const resync = withdrawal ? undefined : await this.announceUninstall(ctx, manifestId); hit once, blob e25af962d8ba to 3bdeeea26dea, build exit 0, preflight marker ablated27395 in 2 built files: dogfood 3 failed / 2 passed, unit 3 failed / 4 passed; restore blob == HEAD, git diff HEAD empty, rebuild + preflight --absent in 6 files, dogfood 5/5. A first mutate leg without the void read is not counted (DTS build exit 1, TS6133). Moved pins: 3 existing uninstall rigs gained a no-op async trigger on their ctx (rig only, no assertion changed); without it 12 tests failed on the new failed outcome. On merged head 7cb1ca3 (main ded67bf merged, closure rebuilt VERDICT command-exit 0, 67/67): pnpm --filter @objectstack/cloud-connection test VERDICT command-exit 0, Test Files 60 passed (60), Tests 754 passed (754); typecheck VERDICT command-exit 0; the dogfood file VERDICT command-exit 0, 5 passed (5).",
"gates": "Local subset on 7cb1ca3: node scripts/pm/dispatch-gates.mjs --commands derived 64 commands; all 64 run with exit captured before any pipe, 64 exit 0. pnpm check:dual-build-cjs-loads first answered exit 3 (PREREQUISITE NOT MET: 7 packages outside the closure had no dist); built them (turbo 43/43, VERDICT command-exit 0) and it exited 0: 112 require entry points across 69 packages load. check:type-check-debt VERDICT command-exit 0, 0 raw tsc errors above ledger. dispatch-gates --ran: 64 derived, 64 run, 0 NOT-MEASURED, 0 UNRUN, a DERIVED zero (all 64 carry an exit code, none is 3). test:repo not run locally (no repo-project test edited, not derived); CI families: PR 27530 check-runs pending at report time, to be read by the seat at ACCEPT.",
"dogfood_battery": "n/a — no
!in the PR title and no (narrowing) arm","mcp_calls": "2 — mcp__github__issue_read x2 (card and triage reads), read-only; plus mcp__claude-code-remote__subscribe_pr_activity on PR 27530 (harness subscription, not a GitHub write). Zero GitHub MCP write tools.",
"api_writes": "2 — (1) relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches, run 40861851173 success, POST /pulls draft = PR 27530, body read back 13740 bytes sent / 13740 stored identical; (2) this report: scripts/pm/post-stamped.mjs over the relay transport (POST /repos/objectstack-ai/objectstack/dispatches, landing as an issue comment on 27395). git push is not a REST write.",
"open_questions": [],
"deviations": [
"The dispatch asked for pr_create with assignees os-sales and pr_request_reviewers os-sales. Not done: pm-dispatch SKILL.md says pr_create 不请不派 and ⛔ 永不派/请席位自身账号 (the review request is the seat's hand-off after ACCEPT + same-head PASS), and os-dev.md says a dev's PR carries no assignees. The PR opened unassigned with no review request; the seat can do the hand-off itself.",
"PR 27344 overlap: its head 355edc0 re-read unchanged before pr_create; bare-clone git merge-tree --write-tree of this head against it exits 0 (tree 674cf2d5f8); hunks disjoint (theirs 214/734/1954/1969/3464, ours 111/303/2291-2308)."
],
"out_of_scope_findings": [
"class: none (read-only inference, not filed) · reach: not measured · the runtime's DELETE /api/v1/packages/:id (packages/runtime/src/domains/packages.ts) withdraws via deletePackage / uninstallPackage and also announces no metadata:reloaded; whether that door leaves flows live depends on what deletePackage's stored-row deletes signal to the engine · dedupe words: packages DELETE flows still active, uninstall metadata:reloaded runtime, deletePackage automation resync · carrier: none · noted in PR 27530 Acceptance notes"
]
}
Generated by Claude Code