Skip to content

finding: a flow-type action's AutomationContext gets the same stamped record stub when the caller cannot read the row — and the flow face has no recordLoadDenied #14244

Description

@os-support-ai

Observed while implementing #14143 on 66ecc50a. ⛔ Unclaimed. Filed rather than fixed: #14143's dispatch order binds that card to the handler predicate, and this is one surface over.

What #14143 fixed, and where it stops

#14143 adds ctx.recordLoadDenied to the script/body action face: when the dispatcher's caller-scope load of the subject row does not deliver it, the handler is told, instead of only receiving ctx.record = { id: recordId } (the id being stamped on precisely because the load failed).

Both dispatch doors now route their load through one producer, loadActionSubjectRecord (packages/runtime/src/action-execution.ts). But the flow branch consumes the same record object and carries no equivalent signal:

  • packages/runtime/src/action-execution.ts — MCP run_action, the action.type === 'flow' branch passes record into dispatchFlowAction;
  • packages/runtime/src/domains/actions.ts — REST /actions, same;
  • dispatchFlowAction then hands it to automation.execute(action.target, { record, ... }) as the run's AutomationContext.record, and seeds params from the same object via seedFlowActionParams.

So a flow started by a row-scoped action on a row its invoker cannot read receives record = { id: <recordId> } — shaped exactly like a legitimate record-less start, with nothing on the context distinguishing the two.

Why it is a smaller claim than #14143, not the same one

A flow action is deliberately not system-elevated: the flow engine receives the caller's identity and honours runAs (ADR-0049), so a runAs: 'user' flow re-derives the caller's scope on its own reads and the stub simply resolves to nothing. The exposure is narrower and conditional:

⛔ Not claimed

Dedupe

One targeted search_issues (repo-scoped REST search answered 403 for this seat, so the channel switch is declared here). 11 results, positive control holds — #14143 itself and the runAs/automation-elevation family (#14201, #14011, #5494, #7900, #3783, #4366) came back, so the query matched semantically rather than returning a silent zero. Nearest neighbours excluded:

⛔ No duplicate.

Repro sketch

Declare a type: 'flow' action on an object with OWD private, targeting a flow with runAs: 'system'. Invoke it as a caller who cannot read the target row, with that row's id. The run's AutomationContext.record should be observed as { id: <recordId> } rather than absent — identical to a record-less start.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions