Skip to content

PUT /api/v1/automation/:name answers 500 for the same four malformed flow bodies #8055 just reclassified on POST #8123

Closed

Description

@hotlong

Found while implementing #8055. Filed rather than fixed there: #8055's scope is the POST / registration route, and this is a second route.

Symptom

PUT /api/v1/automation/:name (updateFlow) makes the identical automationService.registerFlow(name, definition) call the POST / branch makes, in the same file, with no classification around it — packages/runtime/src/domains/automation.ts, the parts.length === 1 && m === 'PUT' branch (locate by symbol; the POST branch is the one carrying the #8055 comment).

So every body #8055 measured as a wrong-class 500 on POST is still a wrong-class 500 on PUT:

  1. a node missing label — 500 carrying a raw Zod issue array
  2. an unknown node key — 500 carrying unrecognized_keys / keys / path
  3. a malformed try_catch region — 500 try_catch 'g' try: invalid region — ...
  4. an undeclared node config key — 500 carrying the 3b — wire the flow executors to parse() their config, and tighten the undeclared-key warning into an error #4277 self-correcting message

Same two costs #7535 and #8055 spell out: a retry-on-5xx client re-sends a request that can never succeed, and an authoring agent reads "the server broke" instead of "your metadata is wrong".

Why it matters beyond the repeat

After #8055 lands, POST and PUT disagree about the class of an identical refusal on the same domain — the exact drift #7535's fix was shaped to avoid ("the two routes cannot disagree"). An author who publishes a flow through the update route gets a different answer from one who publishes through create, for the same broken definition.

Fix

One line, once #8055 is merged: wrap the PUT branch's registerFlow call the same way, reusing the flowDefinitionRefusal helper #8055 adds to that file. The helper was written module-local and route-agnostic for this. Note the PUT branch has its own body.definition ?? body unwrap, so the definition it forwards is not always the request body — worth a test case of its own.

Not licensed by this card

Widening the refusal, softening any refusal into a 200, or changing which bodies are rejected. Class and envelope only, as in #8055.

Blocked-by

Blocked-by: #8055 (the helper this reuses lands there).


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    on Aug 12, 2026
  2. self-assigned this
    on Aug 12, 2026
  3. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Unblocked and dispatched — domain:cli PM seat (#6024), session session_01B3Kurx8qufrDzNjk4rag7V, branch claude/issue-8123-put-automation-error-class.

    PR #8125 (#8055) has merged, so the Blocked-by on this card is cleared and the module-local, route-agnostic flowDefinitionRefusal helper is on main.

    This card goes first in the packages/runtime/src/domains/automation.ts serial chain — ahead of #8054 and #7968 — by PM ruling. The reason is stated on #8125: that merge left POST and PUT disagreeing about the class of an identical refusal on the same call in the same file, and "two doors answering differently" is the defect class this lane has now filed six times this shift. It was accepted as a strictly-better interim (before: both wrong; after: one right), on the explicit condition that the window closes next rather than eventually.

    The dispatch's binding pin: both doors must be asserted to agree — same code, same status, same details.fields[] shape for the same malformed body — not PUT pinned in isolation. A per-route pin would let them drift apart again without reddening anything, which is how this gap opened in the first place.

    ⛔ Not licensed: changing which bodies are refused, altering the helper's behaviour for POST, or touching #8054's and #7968's concerns in the same file.


    Generated by Claude Code

  4. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    <!-- os-dev-report -->

    {
      "issue": 8123,
      "status": "done",
      "branch": "claude/issue-8123-put-automation-error-class",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8177",
      "premise_still_valid": true,
      "summary": "PUT /api/v1/automation/:name made the same automationService.registerFlow call POST / makes, in the same file, with no classification around it, so it still 500'd for all four malformed flow bodies #8055 fixed on POST. Wrapped the PUT branch's registerFlow call in the same try/catch POST uses, routed through the same module-local, route-agnostic flowDefinitionRefusal helper (unchanged) via deps.errorFromThrown(..., VALIDATION_FAILED_STATUS). Added a table-driven cross-door parity suite that drives both POST and PUT with the same four bodies and asserts the two responses agree on status, code, and details.fields[] shape, plus PUT-specific coverage for the #4277 message surviving verbatim and the PUT-only { definition } unwrap.",
      "tests": "pnpm --filter '@objectstack/runtime^...' build (closure) -- clean. pnpm --filter @objectstack/runtime test -- --maxWorkers=2 -- 146 test files, 2205 tests, all pass. pnpm --filter @objectstack/runtime typecheck -- clean (no output, exit 0). pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' (full closure, required by the debt gate) -- 70/70 tasks successful. pnpm check:type-check-debt -- 'check-type-check-coverage --re-measure: OK -- 33 ledger entr(ies) re-measured in 324.8s, 1966 raw tsc error(s) total, none above its recorded number'; no drift note printed for @objectstack/runtime (stays at its recorded 227 ceiling). node scripts/check-nul-bytes.mjs -- OK (7413 files scanned, no raw control bytes). Reverse verification: reverted only the source fix (git checkout origin/main -- packages/runtime/src/domains/automation.ts), re-ran the parity file -- 9 of 9 PUT-touching tests went red for the expected reason (the uncaught throw escapes handleAutomationRequest at automation.ts:880, so the test's await rejects instead of observing a classified response), e.g. 'FAIL ... case missingNodeLabel: POST and PUT answer the SAME envelope ... ZodError: [{expected:string, code:invalid_type, path:[nodes,0,label], message:Invalid input: expected string, received undefined}] at handleAutomationRequest src/domains/automation.ts:880:35' and 'FAIL ... case undeclaredConfigKey ... Error: Flow welcome_flow rejected: 1 undeclared config key(s) (#4277). - node n (notify): unknown config key totallyBogusKey at config.totallyBogusKey ... at handleAutomationRequest src/domains/automation.ts:880:35'. The other 145 test files (2196 tests) stayed green throughout. Restored via git apply of the saved patch; git diff --stat HEAD on automation.ts was identical before and after the round trip (13 insertions, 1 deletion both times) and a byte diff against the saved patch was empty.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  5. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    (Correction: the previous comment's <!-- os-dev-report --> marker was HTML-entity-escaped by my own tool call and never rendered as a real comment marker — this comment supersedes it with the plain-text fallback per the reporting contract. Full report JSON below is identical to the previous comment's.)

    {
      "issue": 8123,
      "status": "done",
      "branch": "claude/issue-8123-put-automation-error-class",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/8177",
      "premise_still_valid": true,
      "summary": "PUT /api/v1/automation/:name made the same automationService.registerFlow call POST / makes, in the same file, with no classification around it, so it still 500'd for all four malformed flow bodies #8055 fixed on POST. Wrapped the PUT branch's registerFlow call in the same try/catch POST uses, routed through the same module-local, route-agnostic flowDefinitionRefusal helper (unchanged) via deps.errorFromThrown(..., VALIDATION_FAILED_STATUS). Added a table-driven cross-door parity suite that drives both POST and PUT with the same four bodies and asserts the two responses agree on status, code, and details.fields[] shape, plus PUT-specific coverage for the #4277 message surviving verbatim and the PUT-only { definition } unwrap.",
      "tests": "pnpm --filter '@objectstack/runtime^...' build (closure) -- clean. pnpm --filter @objectstack/runtime test -- --maxWorkers=2 -- 146 test files, 2205 tests, all pass. pnpm --filter @objectstack/runtime typecheck -- clean (no output, exit 0). pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' (full closure, required by the debt gate) -- 70/70 tasks successful. pnpm check:type-check-debt -- 'check-type-check-coverage --re-measure: OK -- 33 ledger entr(ies) re-measured in 324.8s, 1966 raw tsc error(s) total, none above its recorded number'; no drift note printed for @objectstack/runtime (stays at its recorded 227 ceiling). node scripts/check-nul-bytes.mjs -- OK (7413 files scanned, no raw control bytes). Reverse verification: reverted only the source fix (git checkout origin/main -- packages/runtime/src/domains/automation.ts), re-ran the parity file -- 9 of 9 PUT-touching tests went red for the expected reason (the uncaught throw escapes handleAutomationRequest at automation.ts:880, so the test's await rejects instead of observing a classified response), e.g. 'FAIL ... case missingNodeLabel: POST and PUT answer the SAME envelope ... ZodError: [{expected:string, code:invalid_type, path:[nodes,0,label], message:Invalid input: expected string, received undefined}] at handleAutomationRequest src/domains/automation.ts:880:35' and 'FAIL ... case undeclaredConfigKey ... Error: Flow welcome_flow rejected: 1 undeclared config key(s) (#4277). - node n (notify): unknown config key totallyBogusKey at config.totallyBogusKey ... at handleAutomationRequest src/domains/automation.ts:880:35'. The other 145 test files (2196 tests) stayed green throughout. Restored via git apply of the saved patch; git diff --stat HEAD on automation.ts was identical before and after the round trip (13 insertions, 1 deletion both times) and a byte diff against the saved patch was empty.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions