Skip to content

fix(agent): refuse a single-record action given several records - #2028

Merged
matthv merged 3 commits into
mainfrom
feature/prd-1704-a-single-record-action-given-several-records-runs-on-the
Oct 23, 2026
Merged

matthv merged 3 commits into
mainfrom
feature/prd-1704-a-single-record-action-given-several-records-runs-on-the

Conversation

@hercemer42

@hercemer42 hercemer42 commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

A single-record action triggered on several record ids now answers 422 ("This action can only be triggered on one record at a time.") instead of running on the first record and answering success.

Refs: PRD-1704

What changes

  1. A single-record action on two or more ids. Before: the action ran on the first record only and answered success, so the other records were skipped and nobody was told. After: 422, and nothing runs.
  2. The same request on an action that requires approval. Before: the agent asked for an approval, so the caller created an approval request that later ran on the first record only. After: 422 at submit time, before the permission check, so no approval request is created. Approving one already stored with several ids answers 422 too.
  3. A single-record action on a select-all selection. Before: it ran on whichever record came first. After: 422. The UI already disables single-record actions on a select-all, so it never sends this.
  4. Loading a single-record action's form with several ids (/hooks/load, /hooks/change, /hooks/search). Before: the form loaded against the first record. After: 422.

A single-record action on exactly one id and a bulk action on several ids behave as before. The test asserting that audited ids are narrowed to the caller's authorized subset sent two ids to a single-record action, which is now refused, so it moved to the bulk-action block it actually describes.

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

Squash message

fix(agent): refuse a single-record action given several records

A single-record action triggered on several record ids ran on the first
record only and answered success, skipping the others silently; approving
an approval request naming several ids did the same.

The action route now answers 422 for a single-record action whose
selection names more than one id or is a select-all, before the
authorization check, on execute and on the form hooks alike.

🤖 Generated with Claude Code

Note

Reject multi-record selections for single-record actions in ActionRoute

Single-scope actions now return an UnprocessableError when the selection is select-all, uses excluded records, or contains more than one ID. The check runs in the action-filter builder in action.ts before the selection filter is applied, covering direct requests, approval flows, and form-load hooks.

  • Adds tests for two-ID requests, approval-required execution, signed approvals with two IDs, and select-all/excluded selections; each asserts execution does not run and approval authorization is not requested
  • Adds a load-hook test verifying rejection happens before getForm is called
  • Risk: clients that previously sent multiple IDs to a Single action will now get a 422 instead of the action applying to one record

Changes since #2028 opened

  • Tightened validation in ActionRoute class to reject 'Single' scope actions when the selection is either excluded or does not contain exactly one record identifier [a3a5ec7]
  • Added test coverage for 'Single' scope actions receiving empty selections [a3a5ec7]

Macroscope summarized 2054676.

A single-record action triggered on several ids ran on the first record
only and answered success, since ActionContextSingle reads records[0]. An
API client or an approval request could send such a selection, and every
record past the first was silently skipped.

The route now answers 422 for a single-record action whose selection names
more than one id or is a select-all, before any authorization check: no
approval request is created for it, approving a stored one runs nothing,
and the form hooks refuse it too.

Refs: PRD-1704

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@linear-code

linear-code Bot commented Oct 21, 2026

Copy link
Copy Markdown

PRD-1704

@qltysh

qltysh Bot commented Oct 21, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
Coverage rating: A Coverage rating: A
packages/agent/src/routes/modification/action/action.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@hercemer42 hercemer42 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spec (PRD-1704): conforms.

Comment thread packages/agent/test/routes/modification/action/action.test.ts Outdated
…route handlers

The new cases called the private handlers, so the approval case never ran
the signed-request middleware a real approval passes through. They now go
through the handlers setupRoutes registers, with a signed approval request
naming the two ids.

Refs: PRD-1704

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@matthv matthv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spec (PRD-1704): conforms.

Comment thread packages/agent/src/routes/modification/action/action.ts Outdated
The route refused a single action on several ids or a select-all but
let an empty selection through, which the MCP server sends when
recordIds is omitted: the action then ran on nothing and answered
success, or failed with a raw error from getRecordId. A single action
now runs only on exactly one id, as the Python agent does.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@Scra3 Scra3 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after /validator run 2028-20261022165445-Scra3: nothing blocking posted, 0 preferential or code style, 0 suppressed below all.

@matthv
matthv merged commit 2c2d8b0 into main Oct 23, 2026
50 of 63 checks passed
@matthv
matthv deleted the feature/prd-1704-a-single-record-action-given-several-records-runs-on-the branch October 23, 2026 07:43
forest-bot added a commit that referenced this pull request Oct 23, 2026
## @forestadmin/agent [1.110.5](https://github.com/ForestAdmin/agent-nodejs/compare/@forestadmin/agent@1.110.4...@forestadmin/agent@1.110.5) (2026-10-23)

### Bug Fixes

* **agent:** refuse a single-record action given several records ([#2028](#2028)) ([2c2d8b0](2c2d8b0))

### Dependencies

* **@forestadmin/workflow-executor:** upgraded to 1.35.1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants