You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit b1ff43a
Browse filesBrowse the repository at this point in the historyBrowse files
Merge origin/main into claude/global-actions-unreachable-rw7hpk
Resolves the `/actions` catch-block conflict with #3937, which landed the
opposite decision to this branch's second half and asserted it in
`actions-validation-envelope.test.ts` so it could not drift silently.
#3937 is right about the case it addressed: a handler that RAN and rejected is
a business outcome, not a transport error, and belongs in the payload. That
reasoning does not extend to a request that never DISPATCHED — an unregistered
action has no outcome to report and is indistinguishable from a typo'd URL.
This route already answers the other pre-dispatch failures with a status (403
denied, 400 wrong action type, 503 unavailable), so the not-found exit joins
them as a 404 and everything below the dispatch line keeps #3937's envelope,
`code`/`fields` included.
Reverted from this branch to honour that: the 400 for an action-body throw, the
400 + FLOW_FAILED for a rejected flow, and the errorFromThrown 500 fallback.
The client-side catch in `actions.invoke` stays and is now load-bearing —
`client.fetch` throws on every non-2xx, so without it the routes that just
gained a status would start propagating exceptions into callers that only ever
checked `result.success`.
The wider 200-vs-4xx question is left open for #3913 rather than settled here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011AvZj6cLX7APd7roh2eK4F
Copy file name to clipboardExpand all lines: .changeset/actions-global-key-and-failure-status.md
+22-31Lines changed: 22 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,10 +3,7 @@
3
3
"@objectstack/client": minor
4
4
---
5
5
6
-
fix(actions): reach global actions at their real registration key, and stop serving handler failures as HTTP 200 (#3913)
7
-
8
-
Two independent defects that compounded into "global actions are unreachable,
9
-
and when one fails you are told it succeeded".
6
+
fix(actions): reach global actions at their real registration key, and 404 an action that never dispatched (#3913)
10
7
11
8
**1 — the registration key and the lookup key disagreed.** Both writers
12
9
register an objectName-less action under the literal `'global'`: `AppPlugin`
@@ -29,36 +26,30 @@ single-segment path (`/actions//:action`) routes at `'global'` instead of
29
26
400-ing. A handler registered directly under `'*'` still resolves; the doc
30
27
comments that called `'global'` a "wildcard" are corrected at every site.
31
28
32
-
**2 — every handler failure was wrapped as transport success.** Both the
33
-
success and the failure exit called `deps.success(...)`, which always emits
34
-
`{status: 200, body: {success: true, data}}`. So any failure — a denial, a
35
-
missing action, a deliberate `throw` in an action body — went out as:
29
+
**2 — "no such action" was reported as a success.** The not-found exit called
30
+
`deps.success(...)`, which always emits `{status: 200, body: {success: true,
31
+
data}}`, so a request naming an action that does not exist came back as:
36
32
37
33
```json
38
34
{"success":true,"data":{"success":false,"error":"Action 'log_call' on object '*' not found"}}
39
35
```
40
36
41
37
Every caller that did not hand-unwrap the INNER envelope read the outer
42
-
`success: true` and reported a success that never happened. Failures now exit
43
-
through the dispatcher's error path with a real status:
44
-
45
-
| Failure | Status |
46
-
|:---|:---|
47
-
| No handler registered under any key |**404**, naming the routed object rather than whichever probe ran last |
48
-
| Deliberate `throw` from an action body (`SandboxError`) |**400** with the business message — the mapping `@objectstack/rest`'s `mapDataError` has always used for the identical error |
49
-
| A `flow` action whose flow ran and rejected |**400**, `code: FLOW_FAILED`|
50
-
| Error carrying its own `.status` (e.g. a `FORBIDDEN`) | that status |
51
-
| Record `ValidationError`|**400** with `fields[]` (#3918 parity) |
52
-
| Anything else |**500**|
53
-
54
-
The **success** envelope is unchanged (`{success: true, data: {success: true,
55
-
data}}`), and `client.actions.invoke` / `invokeGlobal` still do **not** throw —
56
-
they fold the new non-2xx shape back into the same `{ success, data?, error? }`
57
-
result, with `error` as a plain string, and keep honouring the pre-#3913
58
-
`200 + inner success:false` shape so a current SDK can still talk to an older
59
-
server.
60
-
61
-
**Migration:** anything reading `response.data.success` off a raw HTTP call
62
-
should read the HTTP status (or the top-level `success`) instead — a failed
63
-
action is no longer a 200. Callers going through `@objectstack/client` need no
64
-
change.
38
+
`success: true` and reported a success that never happened — including the
39
+
shipped console, which showed a green toast (fixed on that side in
40
+
objectui#2963). Nothing **dispatched** there, so it is a **404** now, joining
41
+
the answers this route already gives a status: 403 denied, 400 wrong action
42
+
type, 503 unavailable. The miss also names the **routed** object rather than
43
+
whichever probe ran last (the old fallback said `on object '*'`, an object the
44
+
caller never asked for).
45
+
46
+
A handler that **ran and rejected** is unchanged: HTTP 200 with
47
+
`data: {success: false, error, code?, fields?}`. That is a business outcome,
48
+
not a transport error, and #3937 pins it. The line is "did a handler run" —
49
+
below it the payload, above it the status.
50
+
51
+
`client.actions.invoke` / `invokeGlobal` still do **not** throw. `client.fetch`
52
+
throws on every non-2xx, so `invoke` now catches and folds a dispatch failure
53
+
into the same `{ success, data?, error? }` result with `error` as a plain
54
+
string — otherwise the routes that just gained a status would have started
55
+
propagating exceptions into callers that only ever checked `result.success`.
0 commit comments