Skip to content

fix(runtime): answer 404, not 500, when toggling an unknown automation flow (#7535) - #7558

Merged
os-help merged 1 commit into
mainfrom
claude/issue-7535-toggle-unknown-flow-404
Aug 11, 2026
Merged

fix(runtime): answer 404, not 500, when toggling an unknown automation flow (#7535)#7558
os-help merged 1 commit into
mainfrom
claude/issue-7535-toggle-unknown-flow-404

Conversation

@os-help

@os-helpos-help commented Aug 11, 2026

Copy link
Copy Markdown
Collaborator

Fixes#7535

POST /api/v1/automation/:name/toggle against a flow name the registry does not hold answered 500 INTERNAL_ERROR. It now answers 404 RESOURCE_NOT_FOUND, naming the flow it could not find.

The class was the defect, not the wording. Clients and retry layers branch on it: 5xx means "the server broke, try again", 4xx means "your request was wrong, don't". A typo'd flow name presented as a transient server fault, so any retry-on-5xx caller re-sent — repeatedly — a request that can never succeed.

Cause

toggleFlow on an unregistered name throws a plain Error("Flow '<name>' not found") (packages/services/service-automation/src/engine.ts:2411). It carries no .status, so both dispatcher error exits — HttpDispatcher.errorFromThrown and dispatcher-plugin's errorResponseBase — fell back to their 500 default. Nothing was catching the not-found case and calling it a not-found.

Fix

packages/runtime/src/domains/automation.ts — the toggle branch runs an existence probe before touching the service, and returns 404 in the house envelope naming the flow.

Three deliberate choices worth reviewing:

The endpoint's body arm already met this bar — {"enable": false}, one letter off, is a located 400 naming the offending key rather than a silent enable (#3899). The missing-flow arm now answers in kind: a 404 that names the flow, not a bare status change.

Unchanged: toggling a real flow in either direction, the documented bodyless enable, the strict { enabled?: boolean } validation, and any IAutomationService implementation that omits the optional getFlow? — it cannot be asked whether a flow exists, so its toggle proceeds exactly as before rather than this inventing a 404.

On error.code

Left to derive from the status (RESOURCE_NOT_FOUND), matching the sibling GET /:name on this domain. Two alternatives were considered and rejected:

  • StandardErrorCode's 404 bucket has no flow-specific member (RESOURCE_/OBJECT_/RECORD_/FIELD_/ENDPOINT_NOT_FOUND), and adding one is not this PR's business.
  • packages/specdoes declare AutomationApiErrorCode.flow_not_found — but a repo-wide search finds no producer and no consumer for that enum outside its own shape test and the type-alias pin, so nothing serves it on the wire today. Adopting it here would (a) start wiring up a declared-but-unenforced vocabulary as a rider on a P3 bug fix, and (b) make toggle and GET /:name answer different codes for the identical condition — the exact drift the shared existence probe above was chosen to avoid. Worth its own enforce-or-remove card; flagged, not actioned.

Tests

New: packages/runtime/src/domains/automation-toggle-unknown-flow.test.ts (6 tests). The fake models the real engine — getFlow resolves null for an unknown name and toggleFlowthrows — so a handler that never checks cannot pass.

Every test proved to fail against a mutated source:

#MutationTests that went red
M1Guard deleted entirely (pre-fix behaviour)404-envelope; no-500-by-any-route
M2deps.error(…, 404)500404-envelope; no-500-by-any-route
M3Message stops naming the flow404-envelope
M4if (!existing)if (existing)404-envelope; no-500-by-any-route; real flow still toggles
M5typeof getFlow === 'function' guard droppedcontract-optional getFlow
M6Probe moved ahead of the body checksbody-before-registry ordering

Baseline restored green after each.

Verification

Changeset included (@objectstack/runtime patch). No content/docs/releases/** edits.

Docs drift check: the 20 flagged pages are package-wide @objectstack/runtime references, none of which document this endpoint. The only page that mentions /toggle at all is content/docs/references/api/automation-api.mdx, which is auto-generated and lists no error statuses. No doc asserts the old 500 behaviour. Conversely, docs/qa/platform-checklist/areas/automation.json:982 already specified the negative probe as "expect a not-found error, no state change" — the checklist was right and the implementation was out of step; this change makes reality match it.

…n flow (#7535)
`POST /api/v1/automation/:name/toggle` against a flow name the registry does
not hold answered **500 `INTERNAL_ERROR`**. It now answers **404
`RESOURCE_NOT_FOUND`**, naming the flow it could not find.
The class was the defect, not the wording. Clients and retry layers branch on
it: 5xx means "the server broke, try again", 4xx means "your request was wrong,
don't". A typo'd flow name presented as a transient server fault, so any
retry-on-5xx caller re-sent — repeatedly — a request that can never succeed.
Cause: `toggleFlow` on an unregistered name throws a plain
`Error("Flow '<name>' not found")` (service-automation's engine). It carries no
`.status`, so both dispatcher error exits — `errorFromThrown` and the plugin's
`errorResponseBase` — fell back to their 500 default.
Fixed at the domain handler rather than in a generic catch, on purpose. Which
HTTP status a plain domain error means is the serving boundary's decision (the
rule `validation-failure.ts` already states for `ValidationError` → 400), and
teaching a shared catch to recognise one engine's message string would make
every domain's not-found depend on that prose. The handler instead runs the
**same existence probe `GET /automation/:name` already uses**, so the two routes
cannot disagree about which flows exist.
This brings the missing-flow arm up to the standard the endpoint's *body* arm
already met (#3899), where `{"enable": false}` — one letter off — is a located
400 naming the offending key rather than a silent enable. The refusals compose
in that order: a malformed body is still rejected without the registry being
consulted at all.
Unchanged: toggling a real flow in either direction, the documented bodyless
enable, the strict `{ enabled?: boolean }` validation, and any
`IAutomationService` implementation that omits the optional `getFlow` — it
cannot be asked whether a flow exists, so its toggle proceeds exactly as before
rather than this inventing a 404.
Six tests in `automation-toggle-unknown-flow.test.ts`, each proved to fail
against a mutated source (status 404→500; guard deleted; message stops naming
the flow; `!existing` inverted; optional-method guard dropped; probe moved ahead
of the body checks). Full `@objectstack/runtime` suite green: 123 files, 1982
tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015heAKHUUrVM5GGFgvhf317
@vercel

vercelBot commented Aug 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 11, 2026 5:25am

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/runtime.

20 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx(via packages/runtime)
  • content/docs/api/index.mdx(via @objectstack/runtime)
  • content/docs/api/wire-format.mdx(via @objectstack/runtime)
  • content/docs/automation/hook-bodies.mdx(via @objectstack/runtime)
  • content/docs/concepts/metadata-lifecycle.mdx(via @objectstack/runtime)
  • content/docs/concepts/north-star.mdx(via packages/runtime)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/runtime)
  • content/docs/deployment/index.mdx(via @objectstack/runtime)
  • content/docs/deployment/production-readiness.mdx(via @objectstack/runtime)
  • content/docs/deployment/single-project-mode.mdx(via @objectstack/runtime)
  • content/docs/deployment/vercel.mdx(via @objectstack/runtime)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/runtime)
  • content/docs/kernel/cluster.mdx(via @objectstack/runtime)
  • content/docs/permissions/authentication.mdx(via @objectstack/runtime)
  • content/docs/permissions/authorization.mdx(via packages/runtime)
  • content/docs/permissions/system-context.mdx(via packages/runtime)
  • content/docs/plugins/packages.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/runtime)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/runtime)

2 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/runtime)
  • content/docs/releases/v17.mdx(via @objectstack/runtime)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 11, 2026
@os-help
os-help marked this pull request as ready for review August 11, 2026 07:47
@os-help
os-help added this pull request to the merge queueAug 11, 2026
Merged via the queue into main with commit 22df871Aug 11, 2026
27 checks passed
@os-help
os-help deleted the claude/issue-7535-toggle-unknown-flow-404 branch August 11, 2026 08:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

POST /api/v1/automation/:name/toggle answers 500 INTERNAL_ERROR instead of 404 NOT_FOUND for an unknown flow

2 participants

@os-help@claude