# Pitfalls hit while building this plugin Every entry is a real failure observed while developing `dsh-browser-panel` against DSH `0.1.5-rc.2`. They are written down because each one presents as something else entirely. ## 1. Plugin metadata on the wrong export **Symptom**: the plugin mounts, but every `ctx.tools.register(...)` throws `cannot get property "tools" without inject`, and boot fails with `plugin tree failed to load`. **Cause**: the module exported `inject` / `Config` as *named* exports and a bare `export default apply`. The loader reads metadata from the default-exported function. **Fix**: ```js export const inject = ['tools'] export const Config = z.object({ … }) export function apply(ctx, config) { … } apply.inject = inject // ← the loader reads these two apply.Config = Config export default apply ``` ## 2. `ctx.get()` at apply time misses services that mount later **Symptom**: the WebSocket route answers `404` (the shared `/api` handler), the HTTP route answers `400` (the webserver's catch-all for a throwing handler), and the plugin's own log line never appears. **Cause**: `const webServer = ctx.get('webServer')` ran before the webserver service existed, so `webServer === undefined` and the routes were silently skipped. **Fix**: wait for declarations with dynamic injection — the pattern the shipped API gateway uses: ```js ctx.inject(['webServer', 'connection'], (webCtx) => { webCtx.effect(() => webCtx.webServer.register(route), 'label') }) ``` ## 3. Reaching a service through the context proxy **Symptom**: an HTTP route answers `400` with an empty body (the webserver turns handler errors into `400`), and nothing is logged anywhere useful. **Cause**: the handler called `ctx.connection.requestRejection(req)` while `connection` was not in the plugin's `inject` list. Cordis throws on property access. **Fix**: keep web services optional and pass the *service object* into route factories instead of the context. ## 4. Tool outputs that are not lossless JSON **Symptom**: a tool call fails with a serialization error while its body clearly returned an object. **Cause**: `{ running: false, mode: undefined, … }` — `undefined` has no JSON representation, and the tool registry validates the canonical value before rendering. **Fix**: run every canonical return through a `JSON.parse(JSON.stringify(value ?? null))` cleaner, and prefer `null` over absent fields in status objects. ## 5. Slot registration before the slot is declared **Symptom**: the client half runs (its `