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.md — Platform 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:
- 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.
- 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
- 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.
- 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)
Found in a browser dogfood pass on
objectstack-ai/hotclm(hotclm#45), on@objectstack/*17.4.0. Filed at the destination per that repo'sAGENTS.md— Platform gaps: report, never patch. ⛔ Not triaged: nodomain:*, no priority, no assignee.Symptom — two defects stacked, and the second is the worse one
A
beforeUpdatehook refuses a state transition for a business reason, with a written, user-facing sentence. What the user gets: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:
500 INTERNAL_ERRORsays the platform broke. Nothing broke: a guard evaluated and said no. Any client that branches on 5xx will retry, alert, or page someone.This class has already been fixed once, on a different route
#11684 (closed): "rest: an UNDECLARED hook refusal answers 500 on
/analytics/dataset/querywhere 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.
/datawas right,/analytics/dataset/querywas 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
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
/data— a refusal is a successful evaluation that says no.⛔ 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,
/analyticsroute) · #4431 (closed — sandbox capability denial answering 400 deliberately, the behaviour this route should match)