Every error the host returns carries a stable code. Branch on the code in your plugin logic, and never match the human-readable message. Permission, schema, scope and input errors are not retryable; do not treat a failed grant as a temporary network problem and retry it forever.
The install fails
Work through the error codes that packaging or installing returns in this order:
- the manifest schema and field types;
- whether the entry files exist;
- whether
filesis sealed and the hashes are right; - for a signed package, the publisher key id and the signature;
- whether the package holds duplicate entries, symbolic links or path escapes;
- resource limits such as package size.
Always pack again after changing anything under payload/. Do not "solve" an install problem by turning off schema, hash, signature, CSP or permission checks.
The plugin is not in the list
- Confirm the
.sepluginreally installed, rather than only source having been created; - check the plugin's id, version and state; a newly installed plugin is disabled until you enable it;
- check the workspace or top bar contributions in the manifest;
- do not mistake the empty state of the plugin marketplace for the list of installed plugins.
The workspace is blank or crashes
- Check that
index.html, its scripts and its styles are all infiles; - check whether the page does something CSP forbids: reaching the network directly,
eval, inline scripts; - confirm that
ready()is awaited before any other method is called; - do not
throwwhen initialization fails; show an error panel in the interface.
No event when files are dropped
- Check that the workspace declares
dropswith all three ofextensions,max_filesandmax_bytes; - check that
storage.blobis requested; - extensions are lowercase and carry no dot, and if any file in the batch is unacceptable the whole batch is refused.
The Agent claims success but nothing changes
Check one by one: whether the Agent really read the context, whether the action it used comes from available_actions, whether it really called the action, whether the host returned applied=true, whether the snapshot version went up and last_action.status is applied, and whether the interface rendered again.
If you keep getting PLUGIN_WORKSPACE_SNAPSHOT_NOT_UPDATED:
- first check whether the action handler
throws on its failure path: an exception is not passed back, so bump the version and publish a failure receipt inlast_actioninstead; - check whether
publishis being debounced past the roughly eight-second window for an action to take effect; - check whether errors from
publishare being swallowed (rate limits and oversized snapshots both stop the version from growing); - when changes come in a burst, offer a batch action and publish once when it is done.
The worker never becomes ready
- Is stdout carrying NDJSON protocol frames only, with no banner or debug print?
- Does the handshake echo the request id,
plugin_idandworker_idunchanged, with all five required fields present? - Does
worker.healthreturnreadyordegraded? - Does stderr hold an error such as a missing dependency?
- Are the entry file and the platform right?
The worker crashes often or is quarantined
- Whatever follows the colon in
PLUGIN_WORKER_QUARANTINEDis the tail of stderr from the last exit, so fix that first; - a refused handshake, an invalid health check and non-JSON content on stdout each count as one exit; timeouts do not;
- once fixed, toggle Disable → Enable or install over the plugin to reset the count with no waiting.
Every call fails after one timeout
PLUGIN_WORKER_UNRESPONSIVE usually means the worker's main thread is stuck, and has nothing to do with the handshake:
- first check for
child_process: in the sandbox it throwsERR_ACCESS_DENIEDoutright, so stop callingspawn,exec,forkand the like; - next check for large synchronous file reads and writes, unbounded loops and blocking waits on the main thread;
- do not paper over the problem by raising
timeout_ms.
Permission denied
Compare, in order, what the manifest requests, what you granted, the distribution policy and the current session. Then separate the interface-side "worker": "host" grant from a real worker's grant: the two do not carry over.
A service is missing or conflicts
- whether the dependency is required or optional;
- whether the API major versions are compatible;
- whether the service has been installed and enabled on its own;
- whether a singleton conflict exists.
Error code quick reference
Workspace Bridge error codes are in Workspace and Bridge API, and the JSON-RPC error codes a worker returns itself are in Worker.
Installation and packaging
| Error code | Meaning |
|---|---|
PLUGIN_PACKAGE_UNREADABLE | The package cannot be read: it is not a valid package, the file set is not sealed, or a Skill file does not follow the specification. |
PLUGIN_PACKAGE_TOO_LARGE | The package exceeds the size limit. |
PLUGIN_MANIFEST_INVALID | The manifest breaks the schema or a cross-field rule (permission combinations, icon, word lists and so on); the message names the item. |
PLUGIN_FILE_HASH_MISMATCH | File contents do not match the hashes in files, usually because a file changed and the package was not rebuilt. |
PLUGIN_APP_VERSION_UNSUPPORTED | The desktop app is older than minimum_app_version. |
PLUGIN_PLATFORM_UNSUPPORTED | The current platform is not in platforms. |
PLUGIN_SIGNATURE_INVALID | The signature check failed: the package changed after signing. Pack and sign again, or produce an unsigned package. |
PLUGIN_ICON_INVALID | The icon resource is invalid. |
PLUGIN_I18N_INVALID | The translation declarations are invalid. |
PLUGIN_TYPE_INVALID | plugin_type is invalid. |
PLUGIN_UNSIGNED_INSTALL_DISABLED | Allow unsigned plugins is not turned on. |
PLUGIN_DEVELOPER_MODE_DISABLED | The current build or settings do not allow this capability from a developer source. |
PLUGIN_PUBLISHER_CHANGED | The publisher changed during an upgrade. |
PLUGIN_TRUSTED_WORKER_NOT_APPROVED | The plugin contains a trusted worker that you did not confirm. |
PLUGIN_PACK_FAILED | Packaging failed. |
PLUGIN_INSTALL_FAILED | The install failed. |
PLUGIN_BUILTIN_NOT_REMOVABLE | A bundled plugin cannot be uninstalled. |
Worker (decided by the host)
The host decides the codes below and they reach the caller unchanged; error.message is CODE[:detail].
| Error code | Meaning |
|---|---|
PLUGIN_WORKER_METHOD_NOT_DECLARED | The operation called is not in the handshake's methods, so the request never reaches the worker. |
PLUGIN_WORKER_POLICY_DENIED | You did not grant the execution policy requested (trusted or This-computer mode). |
PLUGIN_WORKER_ISOLATION_POLICY_MISMATCH | A worker declared as sandboxed_plugin asked for This-computer mode. |
PLUGIN_WORKER_EXITED | The worker exited before answering; the tail of stderr follows the colon. |
PLUGIN_WORKER_HANDSHAKE_INVALID | The handshake response lacks a field or the identity does not match; the field name follows the colon. |
PLUGIN_WORKER_HEALTH_INVALID | The status of the health check is neither ready nor degraded. |
PLUGIN_WORKER_FRAME_TOO_LARGE | A single message frame is too large and the process was ended. Pass large data with file tokens or Blob chunks. |
PLUGIN_WORKER_INVALID_RESPONSE | The frame is JSON but not a valid response. |
PLUGIN_WORKER_IO | Reading or writing the standard input and output pipe failed and the process was ended. |
PLUGIN_WORKER_TIMEOUT | The call timed out but the health check that follows answered; the process is kept. |
PLUGIN_WORKER_UNRESPONSIVE | The call timed out and the health check did not answer either; the process was ended. |
PLUGIN_WORKER_CANCELLED | The interface called worker.cancel. |
PLUGIN_WORKER_QUARANTINED | Three exits within five minutes, so the worker is quarantined. |
PLUGIN_WORKER_REMOTE_ERROR | The worker returned a JSON-RPC error, in the form PLUGIN_WORKER_REMOTE_ERROR:<code>:<message>:<data>. |
PLUGIN_WORKER_FAILED | The fallback code: what the host received was not a stable error code. Read the raw message. |
PLUGIN_WORKER_FILE_TOKEN_EXPIRED | The file token has expired. |
PLUGIN_WORKER_DEPENDENCY_MISSING | A service the plugin depends on is missing. |
PLUGIN_SCHEDULE_TRIGGER_NOT_DECLARED | A host.schedule.* call names a trigger that the manifest does not declare. |
PLUGIN_SCHEDULE_INTERVAL_INVALID | The interval is below the declared minimum or out of range. |
Network and host services
| Error code | Meaning |
|---|---|
PLUGIN_HOST_SERVICE_DENIED | services.invoke or net.fetch was refused: the plugin is disabled, the permission is not declared with "worker": "host", or you did not grant it. Not retryable. |
PLUGIN_HOST_NET_DENIED | The URL is not http/https. |
PLUGIN_HOST_NET_INVALID_REQUEST | The request parameters are invalid. |
PLUGIN_HOST_NET_REQUEST_BODY_ENCODING_INVALID | Base64 encoding was declared but the body is not valid Base64. |
PLUGIN_HOST_NET_REQUEST_TOO_LARGE | The request body exceeds the limit. |
PLUGIN_HOST_NET_RESPONSE_TOO_LARGE | The response exceeds the limit. |
PLUGIN_HOST_NET_RESPONSE_NOT_TEXT | A text response was asked for but the response is not text. |
PLUGIN_HOST_NET_JSON_INVALID | A JSON response was asked for but it cannot be parsed. |
PLUGIN_HOST_NET_BLOB_UNAVAILABLE | A blob response was asked for but there is no active workspace or no storage.blob grant. |
PLUGIN_HOST_NET_FAILED | The request failed (connection, timeout and so on). |
Global services
| Error code | Meaning |
|---|---|
SERVICE_NOT_FOUND | The service is not installed. |
SERVICE_VERSION_UNSATISFIED | The service version does not fit the dependency range. |
SERVICE_SINGLETON_CONFLICT | Two instances of a singleton service conflict. |
SERVICE_PERMISSION_DENIED | No permission to call that service. |
SERVICE_UNAVAILABLE | The service is temporarily unavailable. |
SERVICE_DEADLINE_EXCEEDED | The call timed out. |
SERVICE_INVALID_REQUEST | The request is invalid. |
Logs and privacy
Plugin logs and error messages must not contain tokens, credentials, absolute paths, full query strings, raw RPC payloads or your message text.