Skip to content

console: a stored action without type does not run, though the spec defaults type to script; New's update skeleton and older Studio rows are stored that way #26879

Closed

Description

@objectstack-fleet

Source

Found by the #26735 + #26734 card thread (PR #26877, out-of-scope finding and open question), checked by the ui seat on origin/main 5787df4d.

Problem

ActionSchema.type in packages/spec/src/ui/action.zod.ts (~1096) is .default('script'). The console runs the stored row, not its parse. ActionRunner.executeActionSchema (ui/packages/core/src/actions/ActionRunner.ts ~2560) refuses an action with no type and no api, onClick or redirect: "is not executable: no handler is registered…". So a spec-valid action that leaves type to its default is dead in the console. QA G2 (#26734) measured this: adding type: "script" through the API made the button work.

PR #26877 stops Studio from writing new typeless script actions. Two sources of typeless rows remain:

  • New's blank skeleton in ObjectActionsPanel.tsx (~253, addAction) leaves type off on purpose: its comment says the spec defaults it. The action stays typeless until the author's first field-value write, and while it does, the pinned Type control reads "—".
  • Rows already stored without type, by older Studio builds or by hand, stay dead.

Target (the implementer measures and chooses; report the fork)

The spec declares the default, so the reader should honour it. Either the console reads a typeless action as script, at the one place the runner picks the route, or the serve path returns the parsed row. Do not add a second default in each writer. Seeding type: 'script' in New's skeleton is the narrow alternative (the thread's suggestion). It fixes new rows only, and it contradicts that skeleton's own comment, so weigh it against the reader fix.

File surface

Most likely ui/packages/core/src/actions/ActionRunner.ts (route selection), tests beside, one changeset. Possibly ObjectActionsPanel.tsx if the skeleton route is chosen. No packages/spec/** change: the default is already declared.

Acceptance

A stored { name, operation: 'update', patch: { status: 'done' } } with no type, and a stored { name, body: {…} } with no type, both run on the script route in the console. An action with an unknown type is still refused.

Related: #26734, #26735, PR #26877, QA G2.

Activity

  1. added
    bugSomething isn't working
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    on Oct 27, 2026
  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:ui · area:records · pm:queue (finding removed) · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-27T19:56Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01QsR2jTfvjNTaR29ehLk26T
    Account: zhuangjianguo
    Branch: claude/project-thread-kvrw4t (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
    Worktree: objectstack-issue-26879
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: ui/packages/core/src/actions/ActionRunner.ts (route selection), tests beside, one root changeset; ⛔ packages/spec/**, server packages, ObjectActionsPanel.tsx (the skeleton route needs a seat amendment first), any new key, an exported-type change of @object-ui/core (stop on breach; explain in the report)
    Container & model: S, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; reason: the maintainer's standing rule 「只认领不需要 fable 的卡」, 2026-10-27T10:39Z), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the console's action runner (ui core, this lane) refuses a stored action without type, though the spec defaults type to script · the platform path that may cover it: the serve path returning the parsed row, which it does not today · reached by every action New's skeleton or an older Studio build stored without type
    Thread-read: 6452144569
    Premise: holds (ActionRunner.executeActionSchema refuses a typeless action with no api, onClick or redirect at ActionRunner.ts:2560-2575 on origin/main 8074feb6 at 2026-10-27T21:50Z)
    Serial constraints cleared: no other in-flight card or open PR edits ActionRunner.ts

    Dispatched at 2026-10-27T21:54Z to the card thread on that branch; the order lives in the thread's opening message.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    Delivered 2026-10-27T23:24Z by the #26879 card thread (session_01QsR2jUfE6TDKDHQvod289V) for PR #26964.

    {
      "issue": 26879,
      "status": "done",
      "branch": "claude/project-thread-kvrw4t",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26964",
      "head": "e7e7e357",
      "session": "session_01QsR2jUfE6TDKDHQvod289V",
      "premise_still_valid": true,
      "summary": "Fork taken: the runner, not the serve path. ActionRunner.execute resolved the dispatch key as type || actionType || name, so a stored typeless script action ({ name, body }) resolved to its own name, matched no executor and was refused by executeActionSchema. New module-private resolveDispatchType keeps type, actionType, and the name leg when a handler is registered under the name or the action carries a runner-own key (navigate/api/endpoint/onClick/redirect/explicit reload or close, now named once in declaresRunnerBehavior, which executeActionSchema's guard also reads); otherwise a named typeless action takes ActionSchema.type's default 'script' (SPEC_DEFAULT_ACTION_TYPE). Unnamed {} still lands in executeActionSchema and is refused; an unknown type is unchanged. The typeless { name, operation: 'update', patch } shape already reached the route via the operation check ahead of type (premise half-true for that shape); it is pinned again. No export of @object-ui/core changed; no writer, spec or server change.",
      "files_changed": [
        "ui/packages/core/src/actions/ActionRunner.ts",
        "ui/packages/core/src/actions/__tests__/ActionRunner.specDefaultType-26879.test.ts",
        ".changeset/26879-typeless-action-runs-as-script.md"
      ],
      "line_budget": "3 files, +258 / -9 (ActionRunner.ts +61/-9, new test 183, changeset 14)",
      "tests": "at e7e7e357: vitest run packages/core/src/actions/ (locked) 45/45 files, 660/660 tests; new file 8/8. All 383 ui test files naming ActionRunner/ActionProvider/useActionRunner/useConsoleActionRuntime outside core/actions, --maxWorkers=2 in 4 locked batches: 47+45+145+146 files passed, 630+452+1656+1310 tests, 0 failed. Ablation via ablation-replace.mjs (return SPEC_DEFAULT_ACTION_TYPE -> return name, anchor 1->0, blob ecfc5fa6->9f29d4cd): new file 3 failed | 5 passed (typeless body, early refusal, spec-default cases; update case stays green via operation); restored blob ecfc5fa6 == HEAD, git diff HEAD empty. turbo type-check @object-ui/core (covers tsconfig.test.json) VERDICT command-exit 0, cache miss.",
      "gates": "at e7e7e357: dispatch-gates --commands derived 34; all 34 exit 0 (PR-body gates against the final body via GITHUB_EVENT_PATH; check-partof-closing-keyword exit 0 too); vitest run scripts/ (locked) 178 passed | 2 skipped files, 5065 passed | 2 skipped tests; check:type-check-debt (locked) 0 raw tsc errors; --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. ui check:new-line-citations 0 new; control-byte self-scan no hit. PR check-runs: not started at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "deviations": [
        "Base is origin/main 8074feb6; main moved 27 commits before pr_create, none under ui/packages/core/, git merge-tree clean, so main was not merged in.",
        "A first single-worker vitest run over all 383 consumer files printed nothing for 30 min; I killed my own PID and re-ran the same 383 files with --maxWorkers=2 in four batches (all green). No narrowing remains.",
        "Commit carries the repo's model-free trailer pair, not the harness reminder's model-named Co-Authored-By (pre-push check:commit-card-trailers).",
        "Not subscribed to PR activity: CI convergence and landing are the seat's per os-dev.md."
      ],
      "mcp_calls": "5 — mcp__github__issue_read x4 (get #26879, get_comments #26879 twice, get #26734), mcp__claude-code-remote__get_session x1; 0 GitHub MCP write tools",
      "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + assignees zhuangjianguo, run 40601770641, 7118 bytes read back identical); this os-dev-report comment via post-stamped. Plus git push (not REST).",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    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:uipm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions