Skip to content

A hook's business refusal on a script action answers 500 INTERNAL_ERROR and renders no toast — the user is told nothing, and the same class was already fixed on /data and /analytics #17265

Description

@zhuangjianguo

Found in a browser dogfood pass on objectstack-ai/hotclm (hotclm#45), on @objectstack/* 17.4.0. Filed at the destination per that repo's AGENTS.mdPlatform gaps: report, never patch. ⛔ Not triaged: no domain:*, no priority, no assignee.

Symptom — two defects stacked, and the second is the worse one

A beforeUpdate hook refuses a state transition for a business reason, with a written, user-facing sentence. What the user gets:

POST /api/v1/actions/clm_contract/submit_contract
→ 500 INTERNAL_ERROR      ← the body carries the hook's correct business sentence

and on screen: zero toast elements. Measured by counting toast nodes in the DOM after the response landed — not inferred from "I didn't see one".

So:

  1. A deliberate refusal is served as a server fault. 500 INTERNAL_ERROR says the platform broke. Nothing broke: a guard evaluated and said no. Any client that branches on 5xx will retry, alert, or page someone.
  2. The user is told nothing at all. The refusal text exists in the response and never reaches a human. The user presses Submit, nothing visibly happens, and the contract stays where it was. There is no failure state to recover from because there is no failure state shown.

⚠️ Flow failures DO toast in the same build. Only the script-action path is silent — so this is a gap between two sibling paths, not a missing feature.

This class has already been fixed once, on a different route

#11684 (closed): "rest: an UNDECLARED hook refusal answers 500 on /analytics/dataset/query where the same refusal answers 400 on /data — the route's fallback arm treats a business refusal as a server fault."

That is this defect, on a different route. /data was right, /analytics/dataset/query was wrong and got fixed. /actions/… is still wrong. Whatever discrimination the fallback arm learned there has not reached the action route.

⇒ Worth checking whether this is a per-route fallback arm that must be taught three times, or whether one shared place can answer it once. A defect fixed route-by-route will reappear on the fourth route.

Reproduce

git clone https://github.com/objectstack-ai/hotclm && cd hotclm && pnpm install && pnpm demo
# sign in at /_console/, open a seeded draft contract whose guard will refuse
# (hotclm refuses draft -> submitted without a version file or a template-bearing type)
# press Submit; watch the network response and count toast nodes

The consuming app's guard is a legitimate business rule with a written message — the kind of refusal a product is supposed to show its user.

Expected

  1. A hook's business refusal answers a 4xx on the action route, as it already does on /data — a refusal is a successful evaluation that says no.
  2. The console renders the refusal message for a failed script action, as it already does for a failed flow.

⛔ Not claimed here: which status code is right for this route (400 / 422 / something else) — /data's existing answer should settle it rather than a fresh choice. ⛔ Also not claimed: that the console half and the status half belong to the same seat; they plausibly split across repos.

Related

hotclm#45 (the dogfood pass) · #11684 (closed — the same class, /analytics route) · #4431 (closed — sandbox capability denial answering 400 deliberately, the behaviour this route should match)

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions