Surface plugin mutation errors - #12
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Owncast plugin SDKs (JavaScript + Python) and host runtime contract so that mutation-style host calls decode the runtime { error? } result and throw when Owncast rejects an operation, instead of failing silently. It also aligns engine import declarations, mocks, docs, and examples with the new “operation result” return values.
Changes:
- Decode and surface
{ error? }results for chat moderation, user moderation, KV writes, and runtime action-button mutations in both SDKs. - Update the shared engine import/type declarations to reflect host functions that now return a result pointer instead of
void. - Synchronize host-runtime mocks/dev-server hooks, wire protocol docs, author guide, examples, and pin the host-runtime Owncast dependency to the matching commit.
Reviewed changes
Copilot reviewed 11 out of 14 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| sdks/python/owncast_plugin/init.py | Wrap mutation calls with _operation_result(...) and raise on host-returned errors. |
| sdks/js/index.js | Require and validate operation-result payloads for mutation calls; update actions add/clear to use operation results. |
| sdks/js/index.d.ts | Document that mutation APIs now throw when the host rejects operations. |
| host-runtime/plugin/testing/mocks.go | Update mock HostEnv hooks to return error for mutation hooks. |
| host-runtime/main.go | Update demo HostEnv hooks to return error for user moderation hooks. |
| host-runtime/go.sum | Pin github.com/owncast/owncast to a newer pseudo-version matching required behavior. |
| host-runtime/go.mod | Bump Owncast dependency version to the matching commit. |
| host-runtime/cmd/owncast-plugin-serve/main.go | Update dev server HostEnv mutation hooks to return error. |
| examples/python/action-buttons/README.md | Document that action mutations raise and reject the whole batch on failure. |
| examples/js/action-buttons/README.md | Document that action mutations throw and reject the whole batch on failure. |
| engines/javascript/engine.d.ts | Update host import signatures to return PTR for operation-result-returning functions. |
| engines/build_py.py | Update generated Python host import declarations to include return values for operation-result host calls. |
| docs/WIRE_PROTOCOL.md | Update the wire contract to specify JSON {error?: string}-style outputs for mutations. |
| docs/PLUGIN_AUTHOR_GUIDE.md | Document throwing semantics and batch rejection behavior for action mutations. |
Suppressed comments (7)
sdks/python/owncast_plugin/init.py:449
- The error check uses
result.get("error"), which will silently treat an empty-string error as success. Prefer checking for the presence of theerrorkey and keep a fallback message.
if result.get("error"):
raise RuntimeError(result["error"])
sdks/python/owncast_plugin/init.py:462
- The error check uses
result.get("error"), which will silently treat an empty-string error as success. Prefer checking for the presence of theerrorkey (consistent with the SQL/video_config wrappers).
if result.get("error"):
raise RuntimeError(result["error"])
sdks/python/owncast_plugin/init.py:642
- The error check uses
result.get("error"), which can miss failures if the host returns an empty-string error. Check for the key and include a fallback message.
if result.get("error"):
raise RuntimeError(result["error"])
sdks/python/owncast_plugin/init.py:649
- The error check uses
result.get("error"), which can miss failures if the host returns an empty-string error. Check for the key and include a fallback message.
if result.get("error"):
raise RuntimeError(result["error"])
sdks/python/owncast_plugin/init.py:729
- The error check uses
result.get("error"), which can miss failures if the host returns an empty-string error. Check for the key and include a fallback message.
if result.get("error"):
raise RuntimeError(result["error"])
sdks/python/owncast_plugin/init.py:736
- The error check uses
result.get("error"), which can miss failures if the host returns an empty-string error. Check for the key and include a fallback message.
if result.get("error"):
raise RuntimeError(result["error"])
sdks/js/index.js:882
- Same as
add(...):clear()now useshostFns(...), which loses the more actionablepermError(...)guidance for authors who forgetui.modifyin the manifest. Restoring the explicit check also avoids leavingpermErrorunused.
const fns = hostFns("owncast_clear_actions", Permissions.UIModify);
requireOperationResult(
fns.owncast_clear_actions(),
"owncast.actions.clear failed",
);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Both calls hand-rolled their result handling from before the shared operation-result helpers landed in #12, so they were the only mutating APIs whose permission-denial behavior differed between the two SDKs: JavaScript threw, Python returned an empty object. Silence is the worst outcome here. A denied register left the caller with no user id, which it then passed to grantSession, which also returned silently, so a gate login failed with nothing logged on the plugin side. Both auth examples have exactly that shape. Route all four through requireOperationResult / _require_operation_result so a denial raises in both languages. JavaScript also picks up the helper's JSON.parse and non-object guards. Every host result struct is `json:"error,omitempty"`, so testing for the key stays correct. Document the rule once: mutating calls that report an outcome raise, readers return empty, void calls are silent, with fs.write, fs.delete and storage.upload as the deliberate exceptions that hand back their envelope instead.
Mutation calls now throw when Owncast rejects them instead of failing silently.
{error?}result from runtime actions, KV writes, chat moderation, and user moderation in both SDKsBefore:
After:
I ran the host-runtime Go tests, checked the JavaScript and Python sources, and built both shared engines and action-button examples against the matching runtime.
Requires owncast/owncast#5106.