Day 76: v0.5.0 Goes Out
Monday. The release tag that this whole week has been pointing at lands today, after a morning spent clearing CI failures from PR #275 one at a time — five in the first pass, three in the second, two more after that. None of them were the kind of break that would have shipped a regression; they were the kind where the build agent runs the suite in a slightly different order than a developer does and finds the tests whose assumptions about cleanup were too generous. chore: release v0.5.0 is the tiny commit at the end of a long week.
The Plugin Manifest Contract
The biggest under-the-hood change today isn't in the release notes, because it's the kind of thing whose absence is the bug. Every Pinchy plugin — pinchy-audit, pinchy-context, pinchy-docs, pinchy-files, pinchy-web, pinchy-email, pinchy-odoo — ships a JSON manifest describing the config shape it expects. Until today, those manifests were aspirational: the writer in openclaw-config would happily emit a plugin entry that the plugin itself couldn't deserialise, and the failure mode would be a startup crash inside OpenClaw with a stack trace that pointed at the plugin rather than at the writer.
The fix is two pieces. The first is a loadPluginManifest helper plus an Ajv-based validatePluginEntry that runs in the writer's hot path: every plugin entry gets validated against its manifest before the config is committed to disk. A mismatch fails fast with a message naming the plugin, the offending field, and the expected shape — the writer's problem to fix, not OpenClaw's to crash through. The second is a contract test per plugin that lives in the writer's test suite: pinchy-web, pinchy-odoo, and pinchy-email each had their configSchema tightened to additionalProperties: false in the same session, because a permissive schema with no contract test is a schema that drifts. The three smaller plugins — pinchy-context, pinchy-audit, pinchy-docs — got their contract tests today; pinchy-files's existing test was upgraded to the same Ajv-based shape so all seven are now checked the same way.
A small refactor fell out of the pass: redundant plugins.entries.<id>.enabled=false stamps got dropped (the manifest already pins the default), and disabled bundled plugins now keep their position in the entries array instead of being rewritten to the bottom on every regenerate. Both were the kind of thing that wasn't broken until the inotify watcher started caring about byte-for-byte parity between writes; once it does, every gratuitous reorder costs a cascade reload.
External Plugins Get HTTP Mocks
The other big push of the day is E2E coverage for the plugins that talk to third-party APIs. Until today, pinchy-web (Brave Search) and pinchy-email (Gmail) were tested at the unit-level only, because the alternative — calling the real APIs from CI — is the kind of trade-off that bites once a quota is exhausted at 2am. The fix is per-plugin HTTP mocks: a Brave-mock server that speaks the Brave Search REST shape, a Gmail-mock that speaks the Gmail API shape, both started by a Docker Compose overlay (compose.brave-test.yml, compose.email-test.yml) that the E2E suite layers on top of the integration compose. The plugin's BRAVE_API_BASE_URL and Gmail API base get pointed at the mock; the rest of the plugin behaves identically to the production deployment.
The matching Playwright specs landed today: a pinchy-web spec that asserts the plugin loads, the credentials shape arrives intact, and a search round-trip against the mock returns the expected result; a pinchy-email spec doing the same for an email send. Both wire into a new test:e2e:web npm script and a fresh web-e2e CI job that runs alongside the existing integration suite. The job is fast because the mocks are local; the coverage is honest because the plugin code under test is the same code that ships.
Two assertions landed in the same pass that catch the regression class this whole apparatus exists to prevent. The first asserts that every external plugin has a spec, a compose overlay, a CI job, and an npm script — so adding a new plugin without wiring its E2E is a CI failure, not a future surprise. The second asserts that every internal plugin (one that doesn't talk to a third party) shows up in the integration E2E suite, by name. Adding a plugin to the codebase without exercising it is now structurally caught.
Pre-Warming and the Disabled Defaults
A ~50-second cold-start improvement landed in the same window, attacking two parts of the same staging-observed symptom: a fresh container takes ~75 s before [gateway] ready, and the first Pinchy calls back up behind the still-blocked event loop into a stuck-loader UI. Two changes in one commit. First, four bundled OpenClaw plugins Pinchy never uses get filtered out of the generated config: acpx (Agent Client Protocol bridge for desktop clients), bonjour (mDNS gateway advertiser), device-pair (QR-code pairing flow), phone-control (phone-node command arming). Each is structurally correct in the desktop deployments OpenClaw was originally written for; each is dead weight in the container deployment Pinchy is. Second, the bundled-plugin runtime-deps cache (~48 s of [plugins] staging bundled runtime deps on first boot, doing an npm install of ~15 packages into /root/.openclaw/plugin-runtime-deps/) gets pre-warmed at image build time — we boot the gateway once during the build with a minimal config, wait for the cache to materialise, kill the gateway, and strip everything from /root/.openclaw except the cache itself. Measured on 2-vCPU staging: gateway ready moves from ~75 s to ~48 s, and the staging-bundled-runtime-deps line disappears from the log entirely.
Plugin Loader Classification
A smaller but load-bearing refactor: the plugin loader now classifies plugins as internal or external at load time and exposes that classification to the rest of the system. Internal plugins (pinchy-context, pinchy-audit, pinchy-docs, pinchy-files) ship inside the Pinchy container and don't talk to anything outside the deployment; external plugins (pinchy-web, pinchy-email, pinchy-odoo) reach a third-party API and need credentials. The two need different treatment in three places — the credential redactor, the E2E coverage check, and the audit log's plugin.kind field — and having a single source of truth for the classification stops the three from disagreeing. A matching test asserts the classification stays consistent if a new plugin gets added without the classifier being updated: the loader fails to start rather than guessing.
The Boot Path Gets Fixed
One real bug got fixed during the CI shakeout that's worth flagging on its own. The Pinchy-first startup path — where Pinchy comes up before OpenClaw on a fresh deployment — had two device-pairing failures: the device-approval handshake was racing the OpenClaw token issuance, and the issued token wasn't being adopted by the in-process gateway client on first read. The fix seeds openclaw.json with a draft device record before either side comes up, waits explicitly for the token to materialise, and adjusts the CI health check to gate readiness on the token's presence rather than on the gateway port alone. The matching test asserts the gateway is connected after Odoo regenerates its config — a regression class we hit twice last week and didn't want to hit a third time.
The Headline Item
The visible release notes are short and the changes underneath them are sweeping. SecretRef plumbed end-to-end, image version pinning moved to .env, BETTER_AUTH_URL documented for HTTPS deployments, Telegram channels hot-reloading their auth profile. Each of those was a week of work by itself; landing them together as v0.5.0 is the kind of release that sets the next quarter's expectations rather than a single feature. The decommissioned trial Lambda from Day 69, the licence-with-teeth work from Day 70, the agent-create cascade fix, the audit PDF export, the Saturday hardening pass — all of it under one tag now.
Day 76
The shape of a release that's been pointed at for two months is that the release day itself is mostly the morning's CI shakeout, the afternoon's plugin contract work, and the press of the tag button at the end. The plugin manifest contract is the kind of thing that will quietly catch a class of bug for the next several releases — every time the writer drifts from a plugin's expectation, the contract test will fail before the code lands. The mocks for Brave and Gmail are the same shape: a CI job is the floor; the plugins beneath it are now exercised in the way their production users will exercise them. The next week is post-release maintenance and the inevitable v0.5.1 — the things that only show up once the install base is broader than the team that wrote it.