Skip to content

Enforce automation length caps at the CLI boundary, not on stored rows - #2219

Closed
SawyerHood wants to merge 2 commits into
mainfrom
fix/2166-automation-cap-validation
Closed

Enforce automation length caps at the CLI boundary, not on stored rows#2219
SawyerHood wants to merge 2 commits into
mainfrom
fix/2166-automation-cap-validation

Conversation

@SawyerHood

@SawyerHood SawyerHood commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

What was wrong

bb automation create and bb automation update built the agent and script execution payloads from argv and handed them straight to the service, skipping the request-side Zod length caps that the automations_create/automations_update RPC routes apply. An over-cap --prompt (8,039 chars against the 8,000 cap) was therefore persisted, and the too_big error the user saw came from re-parsing the stored row for the response. Because that same capped schema (automationAgentExecutionSchema) was also used to parse stored rows, every later list, show, and repairing update for the project failed with the same too_big issue, and list took the healthy automations in the project down with it. Report: https://get-bb.github.io/reports/issues/2166.html

What changed

The length caps were request policy applied in the wrong place. The same Zod schemas that parse incoming create/update input also parse the stored execution and trigger_config columns, so an over-cap value that got past the CLI was written and then made the row unreadable on every subsequent read.

  • Split request policy from the stored shape, on both columns. The stored automationAgentExecutionSchema, automationScriptExecutionSchema, and automationScheduleTriggerSchema no longer carry the prompt, script, scriptFile, cron, or timezone caps. New *RequestSchema variants (automationAgentExecutionRequestSchema, automationScriptRequestSchema, automationTriggerRequestSchema) add the caps back and are what createAutomationInputSchema / updateAutomationInputSchema compose, so the RPC routes still reject over-cap input before any write. A row that already exceeds a cap now stays readable by list/show and repairable by update.
  • Enforce the same policy at the CLI argv boundary. bb automation create/update builds its execution and trigger from argv and calls the service directly, bypassing the route schemas. buildExecution, buildAgentExecutionUpdate, and buildTrigger now parse with those request schemas, so an over-cap --prompt, --script, or --cron is rejected before anything is persisted, and the CLI and the RPC route agree on one policy.
  • Make service.list skip a malformed row with a warning, like overview already did, so one bad row cannot fail the whole project listing.

The one-shot trigger variant is unchanged (it carries no string). The timezone cap is unreachable through the CLI in practice — no valid IANA name approaches 100 characters — so it is asserted at the schema level only.

How you verified

Twelve regression tests across two blocks in plugins/automations/src/automations.test.ts; each behavior test was run against the pre-fix source and fails there (6 execution-cap tests, 5 of 6 trigger-cap tests — the sixth is a guard asserting the over-cap cron is itself valid, so the others cannot degrade into "invalid cron rejected").

  • pnpm exec turbo run test --filter=bb-plugin-automations --force — 5 files, 78 passed.
  • pnpm exec turbo run build typecheck lint (repo-wide) — 92/92 tasks.
  • node scripts/check-provider-literal-ratchet.mjs --base origin/main — 18 references across 8 core files, all allowlisted.
  • Live against an isolated scripts/bb-dev-app instance: an over-cap --prompt (8039 chars) and an over-cap --cron (177 chars, valid 5-field expression) each exit 1 with too_big and write no row; an over-cap --cron on update leaves the stored trigger byte-identical; a row seeded with a 177-char cron is listed, shown, renamed, paused, resumed, and repaired, all exit 0; the sweep advances that row and claims a scheduled run instead of logging invalid stored configuration every tick.
  • Live plugin-RPC HTTP calls confirm the routes still reject pre-write: automations_create returns HTTP 400 invalid_input with path: ["trigger","cron"] and with path: ["execution","prompt"], and the automations table row count is unchanged after both.

No server↔daemon wire contract changed, so HOST_DAEMON_PROTOCOL_VERSION is unchanged. No new CLI flag, command, or configuration knob, so no guide or skill surface needed an update.


Fixes #2166

AGENT GENERATED: by Claude Opus 5

@SawyerHood
SawyerHood force-pushed the fix/2166-automation-cap-validation branch from a4c8dc2 to ee868a4 Compare August 24, 2026 23:29
SawyerHood and others added 2 commits August 24, 2026 18:04
`bb automation create/update` built agent and script executions from argv
without the request-side Zod caps the RPC route applies, so an over-cap
`--prompt` was persisted and only rejected when the stored row was
re-parsed for the response. Because the same capped schema also parsed
stored rows, every later `list`, `show`, and repairing `update` for that
project failed with the same `too_big` issue.

- Parse agent executions and partial agent updates in the CLI with the
  same request schemas the RPC route uses, and parse inline/file script
  content with the shared script cap, before anything is persisted.
- Split request policy from the stored/response shape: the prompt,
  script, and scriptFile caps now live only on the request schemas, so
  rows that already exceed a cap stay readable and repairable.
- Make `service.list` skip a malformed row with a warning, like overview.

Fixes #2166

Co-Authored-By: Claude <noreply@anthropic.com>
PR #2219 closed the #2166 write-then-fail mechanism on the execution
column but left it intact on the trigger column, so the class the PR
claims to close was still open one field over.

`automationScheduleTriggerSchema` carried the cron and timezone caps
and was simultaneously the RPC request shape and the stored-row parser
(`parseAutomationTrigger`, used by every read: list, show, update,
pause, resume, and the sweep). `buildTrigger` handed an unparsed value
straight to `service.create`/`service.update`. A valid but over-cap
`--cron` (60 comma-separated minutes is 177 characters and parses fine)
was therefore committed, then rejected on read: `create` exited 1 with
the row already written, `show`/`pause`/`resume` and any later `update`
exited 1, `list` silently omitted the row through the try/catch this PR
added, and the sweep logged "invalid stored configuration" every tick
forever. Updating a healthy automation with an over-cap cron poisoned
it the same way. The RPC route rejected the same input before the
write, so the CLI and the route disagreed on policy.

- Remove the caps from the stored trigger shape so an already-persisted
  over-cap row stays readable and repairable, matching the execution
  shape and the comment at rpc-types.ts.
- Add `automationTriggerRequestSchema` (the schedule variant plus the
  caps; the one-shot variant carries no string and is unchanged) and
  compose it into `createAutomationInputSchema` and
  `updateAutomationInputSchema`, so the RPC routes keep rejecting
  pre-write.
- Parse in `buildTrigger` with that request schema, so argv is held to
  the same policy as the route before anything is persisted.

The timezone cap is unreachable through the CLI in practice, because no
valid IANA name is anywhere near 100 characters; it is asserted at the
schema level only.

Co-Authored-By: Claude <noreply@anthropic.com>
@SawyerHood
SawyerHood force-pushed the fix/2166-automation-cap-validation branch from ee868a4 to 15482ec Compare August 25, 2026 01:06
@SawyerHood SawyerHood closed this Aug 25, 2026
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.

automation update writes an over-cap prompt despite rejecting it, then no read of that project's automations succeeds

1 participant