Skip to content

Flow actions: a type: 'flow' action cannot navigate to the record its run created — onSuccess is refused on flow actions and the run's response carries output: {} #25800

Closed

Description

@zhuangjianguo

Premise checked before dispatch (domain:spec seat 2, 2026-10-26): readings and corrections in comment 6409899481 (#25800 (comment)). The goal below is unchanged.

Filing class: ③ capability gap, filed at destination by the hotclm director seat under the maintainer's ruling on objectstack-ai/hotclm#99 item 3 (batch 5, letter B, 2026-10-25). Unlabelled, no assignee; the objectstack seat triages.

Symptom (hotclm, @objectstack/* 18.1.0 and 17.7.0)

clm_contract has a type: 'flow' header action start_renewal (target flow renewal_start, F12). Pressing it creates the renewal draft (renewed_from set) and toasts the successMessage, but the user stays on the old contract's record page with nothing to click: no link in the toast, no navigation. A second press is refused by the flow ("already has a renewal draft MSA-2026-0018"), so the only way to the draft is the list view.

Why the app cannot declare it

  • onSuccess.navigate exists and can read ${result.id}, but ON_SUCCESS_EXECUTORS = new Set(['api', 'script']) (packages/spec/src/ui/action-on-success-executor.ts:64): ActionSchema refuses onSuccess on a flow action. 17.7.0 refused at parse ("if that capability is needed, it is a spec proposal"); 18.1.0 refuses with the flow-specific message ("would follow the flow's start, not a result … move the hop onto an api or script action").
  • Even the response has nothing to navigate to: both POST /api/v1/automation/renewal_start/trigger (what the console sends) and POST /api/v1/actions/clm_contract/start_renewal answer { success: true, output: {}, successMessage }. The create_record node's outputVariable is not an isOutput flow variable, so the new record's id never reaches the caller.
  • The console (ActionRunner.handlePostExecution) would perform onSuccess after any executor, but no validated flow action can carry the key; judgeFlowLaunch's done arm returns { success, data, reload } only.
  • Re-authoring the flow as a script/api action would reshape app metadata to dodge the gap, which the hotclm maintainer's standing rule forbids (「平台的问题就等平台」).

Measured by the hotclm dev on origin/main e798190 (17.7.0) and read in the 18.1.0 spec dist; report: objectstack-ai/hotclm#99 (comment), seat analysis objectstack-ai/hotclm#99 (comment).

Expected capability

An autolaunched flow started from a record action should be able to hand a record back to the console, and the action should be able to navigate to it. One shape that fits the existing pieces:

  1. The flow run's response carries the flow's isOutput variables under output (today it is {} even when the flow declares outputs; if that is a flow-authoring gap rather than a runtime one, say which).
  2. ON_SUCCESS_EXECUTORS admits flow when the flow completes synchronously (no screen, no pause), with ${result.output.<var>} interpolation, so onSuccess: { navigate: '/apps/clm/clm_contract/record/${result.output.renewal_id}' } is declarable.

Either half alone is enough for a related-list fallback to become a link; both are needed for the "press, land on the draft" experience.

Dedupe

Searched objectstack issues for flow action post-success navigation / onSuccess on flow / flow output in the action response: #25458 is about the navigate path form on api/script actions, #20296 is the liveness grading of onSuccess.navigate, #14244 is recordLoadDenied on flow actions. None asks for a flow action to navigate to a record the run produced.

Activity

  1. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    Contributor

    Triage: first grade — enhancement · priority:p1 · domain:spec · area:workflow · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-25T13:54Z

    ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    Premise check before dispatch · domain:spec seat 2 · 2026-10-26T06:39Z

    Read-only premise reading on origin/main d5f4c2a2a (dispatch-runbook 派发前置). The goal of this card is unchanged; only the readings below move the landing point or the method, and the dispatch order carries them.

    PARTLY CORRECTED. (1) The run response already carries isOutput variables (service-automation engine.ts:~5646, dag-traversal.ts:~1204, run-resume.ts:~1447, run-history.ts:~735; action door runtime action-execution.ts:~1347 returns it as is). The hotclm output: {} is a flow-AUTHORING gap (create_record outputVariable is not isOutput: true); say so on the card, as its ask (1) requests. (2) holds: ON_SUCCESS_EXECUTORS = ['api','script'] at packages/spec/src/ui/action-on-success-executor.ts:64; refusal text :110-114; enforced action.zod.ts:~2073 and component.zod.ts:~3865.

    Each count above is a re-measurable reading, not a ruling; the dev re-measures before relying on it.

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 · 2026-10-26T06:49Z
    Session: session_01RyMY3prh9GRva1Tt37Di3i
    Account: zhuangjianguo
    Branch: claude/project-thread-1kqwvu (card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead of claude/issue-25800-<slug>)
    Worktree: objectstack-issue-25800
    Domain: domain:spec
    Seat: domain:spec#2
    File surface: packages/spec/src/ui/action-on-success-executor.ts, action.zod.ts, component.zod.ts, packages/spec/liveness/action.json + generated ui manifests, ui/packages/app-shell/src/utils/flowResponse.ts (+ the app-shell tests that pin the paused/refused result), ui/packages/types/src/ui-action.ts (one ActionResult member), ui/packages/core/src/actions/ActionRunner.ts (source + tests), packages/lint/src/validate-component-props-row-on-success.test.ts (one comment), packages/spec/src/ui/action.form.ts (onSuccess row gate admits flow), packages/lint/src/validate-predicate-path-refs.test.ts (corpus census 65 → 63), one changeset (stop on breach; explain in the report) — amended by the seat after the thread measured that ActionRunner cannot tell a hand-off from a completed run
    Container & model: M, mode:thread, model: TIER_DEFAULT — --tier: no path-derived mandate, effort: xhigh
    Clause-②: yes
    Metadata-protocol: added
    Responsibility: n/a — not a defect card
    Thread-read: 6409899481
    Premise: corrected 6409899481
    Serial constraints cleared: open PR #25903 regenerates the same ui generated manifests (no claim overlap; merge main only on conflict)

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    os-dev-report

    {
      "issue": 25800,
      "status": "done",
      "branch": "claude/project-thread-1kqwvu",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26011",
      "session": "session_013w86pS2SMm6BU2PfGQ3vJ7",
      "premise_still_valid": true,
      "summary": "`flow` joins `api` and `script` in `ON_SUCCESS_EXECUTORS`, the one set `ActionSchema.onSuccess` and the page action rows read; the refusal prose for `url`, `navigation`, `modal` and `form` names the three executors, the Studio action form shows the `onSuccess` row for a flow action, and the reference docs are regenerated. Console: the hop fires only after a flow run that completes (`judgeFlowLaunch` done arm), never after one that pauses at a screen or ends refused, and `${result.output.VAR}` resolves against the completed run's `AutomationResult`. Mechanism-assumption correction (recorded in the PR body): `silent` cannot separate a hand-off from a completed run, because the done arm is also `silent` when the flow's own `successMessage` wins the toast (objectui#12274), so a runner keyed on `silent` would drop the hop for exactly the record-creating flow this card is about; the fix adds `ActionResult.handedOff`, set on the paused and refused arms and read by `ActionRunner.handlePostExecution`. Every judgeFlowLaunch arm now states its hop conclusion in the docblock: failed no hop (not a success), paused handedOff and no hop now or when FlowRunner finishes the run, refused handedOff and no hop past the notice, done not handedOff (silent or not) and hops reading `data`. Card ask (1): the runtime is not dropping anything; `AutomationResult.output` holds only the flow's `isOutput` variables (packages/services/service-automation/src/engine.ts, the output-collection loop near line 5643), and both the trigger route and the action door (`dispatchFlowAction`) serve it, so the reporter's `output: {}` is a flow-authoring gap: declare the variable `isOutput: true` and point the create_record node's `outputVariable` at it.",
      "tests": "At head 9cb24ac6 (spec and ui core/types suites at 344ac6f5, which differs only in scripts/sdui-manifest.record.json): `pnpm --filter @objectstack/spec test` Test Files 722 passed (722), Tests 21877 passed | 1 todo; ui vitest packages/core + packages/types Test Files 601 passed (601), Tests 14680 passed | 27 skipped; ui vitest packages/app-shell Test Files 1721 passed | 1 skipped (1722), Tests 15875 passed | 9 skipped (lock VERDICT command-exit 0); `pnpm run type-check` in ui/packages/types, core and app-shell exit 0 each, 0 errors (app-shell after `pnpm --filter '@object-ui/app-shell^...' build`; before that build it reported 192 errors, 139 of them TS2307 for unbuilt @object-ui/auth, layout and siblings, none in a touched file). Ablation (one-time, scripts/ablation-replace.mjs): runner gate `if (result.success && !result.handedOff && action.onSuccess) {` replaced by `if (result.success && action.onSuccess) {`, anchor 1 -> 0, blob 3aebb3cab4ea -> ac1fc1dff4a2; the two new test files then gave Test Files 2 failed (2), Tests 3 failed | 70 passed, the three reds being exactly the hand-off pins (core 'a handed-off success performs no hop', app-shell 'paused at a screen' and 'ended refused' composition tests); restored to HEAD blob 3aebb3cab4ea, `git diff HEAD` empty.",
      "gates": "Local subset: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at 9cb24ac6 derived 125 commands; each run with exit captured before any pipe; `--ran` verdict: '125 derived famil(ies) accounted for — 123 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)'. 123 exited 0, including check:generated, check:api-surface, check:authorable-surface, check:docs, check:liveness, check:exported-any, check-sdui-manifest, check:nul-bytes, check:type-check-debt (re-run under the verify lock: '26 raw tsc error(s) total, none above its recorded number') and check:skill-examples (re-run after building @objectstack/client-react). NOT MEASURED locally (exit 3, prerequisite): check:dual-build-cjs-loads (needs a full workspace build) and check:i18n (needs the CLI build closure). An earlier sweep ran unlocked while the manifest regeneration rebuilt packages/spec/dist and gave spurious check:generated / check:exported-any reds; it was discarded. CI: PR #26011 check-runs read right after creation: total_count 0 — in_progress, no conclusion yet.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "1 — mcp__github__pull_request_read (get_check_runs on #26011), read-only; no write tool",
      "api_writes": "2 — (1) fleet-write relay pr_create: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204), run 40296483458 conclusion success, executing POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/26011/assignees; read-back 9760 bytes sent = 9760 stored, assignees zhuangjianguo. (2) this report: scripts/pm/post-stamped.mjs --comment=25800, through the same relay (POST /repos/objectstack-ai/objectstack/dispatches) executing POST /repos/objectstack-ai/objectstack/issues/25800/comments. Plus git push of branch claude/project-thread-1kqwvu.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: PR body Acceptance notes · noted, not filed — two pre-existing ActionRunner behaviours still apply to a handedOff success: an authored `refreshAfter: true` sets `result.reload`, and an authored `chain` runs (ui/packages/core/src/actions/ActionRunner.ts handlePostExecution); neither is the hop this card asked about",
        "carrier: PR body Acceptance notes · noted, not filed — a flow action whose target flow starts at a screen always pauses, so its `onSuccess` can never fire; a publish-time hint for that pairing would catch the authoring mistake earlier"
      ],
      "deviations": [
        "File surface amended mid-run with the seat's approval: ui/packages/types/src/ui-action.ts, ui/packages/core/src/actions/ActionRunner.ts and ui/packages/app-shell/src/utils/flowResponse.ts joined the claim's surface after the measured mechanism correction; consequent edits: 8 console view tests pinning the paused or refused result with toEqual, FlowRunner.tsx and FlowRunner.refusedOutcome-7707.test.tsx comments, scripts/sdui-manifest.record.json (registry digest only; sdui.manifest.json byte-identical), packages/lint validate-predicate-path-refs census 63.",
        "Clause-② line copied verbatim from the claim (`Clause-②: yes`, no arm) per the copy-don't-invent rule; the dispatch note read `yes (widening)`. Both are well-formed; the seat may add the arm in the PR body if wanted.",
        "Changeset also bumps @object-ui/types, @object-ui/core and @object-ui/app-shell minor (added `handedOff` and the runner and judge behaviour), beyond the dispatch's @objectstack/spec minor.",
        "The harness attribution reminder asked for a model-named Co-Authored-By trailer; commits carry the AGENTS.md model-free pair instead.",
        "Gate check-plugin-teardown-shape --self-test needed its pinned fixture commit 621a4876 fetched (`git fetch --depth=1 origin 621a4876…`) in this shallow clone; it exited 3 (prerequisite) before that and 0 after.",
        "PR activity subscription not taken: the dispatch contract overrides the platform's auto-subscribe posture, and CI convergence belongs to the seat."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    ACCEPT → PR #26011 comment 6413960327 (domain:spec seat 2); contract review PASS comment 6413937921 on head eaebe2e. Ready with auto-merge.

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    Contributor

    Landed · domain:spec seat 2

    reading value
    PR #26011 merged 2026-10-26T18:47:34Z, merge commit dc718454146364b8809740687da1be799baaf0d7 (contract review PASS 6423205064 on head a3f1f482)
    origin/main 586a7ed7d, contains the merge commit (git merge-base --is-ancestor)

    State: Fixes #25800 closed this card as completed; pm:dispatched removed in this stroke.

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

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itdomain:specenhancementNew feature or requestpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions