Day 85: Naming the Version
Wednesday. The first day of the week without a release on the calendar in a while. The work shifts to the two threads that the release pace had pushed off: an end-user gap in verifying what's actually running after an upgrade, and an odoo-sync limitation that staged out as the Create buttons for the new operator templates don't enable, even though the modules are present. Both turn out to be the same shape — Pinchy claiming a feature works without the user being able to confirm it.
What Version Is Running
The gap. End users upgrading Pinchy had no reliable way to confirm which version was actually live after a docker compose up -d. /api/health returns {status:"ok"} and nothing else. /api/diagnostics returns the version but lives behind the Domain Lock, so a typical verify on localhost:7777 curl returns a 403 on the deployments that have Domain Lock enabled (which is the deployments that take verification most seriously). docker inspect on the image returns an empty org.opencontainers.image.version label, because the Dockerfile wasn't actually wiring the build-arg into it. Three paths to which version is this, all dead ends.
The fix is two endpoints' worth of structure. The first is a new public GET /api/version that returns {pinchyVersion, openclawVersion, build, nodeEnv} with Cache-Control: no-store, added to the Domain Lock's EXEMPT_PATHS alongside /api/health. Public on purpose — version is not sensitive, monitoring tools and shell curls both need to hit it without a session, and the alternative is the documented path being wrong. The second is OCI labels on Dockerfile.pinchy and Dockerfile.openclaw populated from PINCHY_VERSION and PINCHY_BUILD_SHA build-args at image-build time. org.opencontainers.image.version, .revision, .source, .title — the labels a customer's image-scanning pipeline expects to see populated.
The matching docs change updates the upgrade verification step from docker compose logs pinchy --tail 50 (which only proves the container started, not which version it is) to curl localhost:7777/api/version (which proves the version directly). A small docs cleanup followed: the Domain Lock guide had a localhost trap — the previous text told users to verify the lock by curling the canonical URL, but on a fresh deployment the canonical URL hasn't been DNS-set up yet and the curl returns a generic connection refused with no signal about whether the lock is the cause. The new text suggests verifying the lock with the public /api/version path first, which proves Pinchy is reachable, and then verifying the lock itself with a wrong-Host curl.
BETTER_AUTH_URL, Named Properly
A subtler docs fix landed alongside. The startup warning that fires when BETTER_AUTH_URL is unset had been saying the variable still controls Better Auth callback URLs — accurate three releases ago and progressively less accurate since. Today's auth.ts reads its trusted origins from the Domain Lock config or the request host, not from BETTER_AUTH_URL; the variable now only controls the outbound URLs Better Auth writes into email-verification and password-reset links. An admin reading the previous warning couldn't tell whether the variable was something to keep setting or legacy to drop. The new warning is explicit: set it if you send password-reset emails through Pinchy and want them to point at the right hostname; drop it if you're on SSO-only with no email/password flow.
The shape of the change is a sentence in a startup warning, but the time it saves is hours per customer who would have otherwise opened a support ticket asking do I need this.
14 of 22 Templates Were Quietly Disabled
The other big thread today started as a click-through against staging for v0.5.4. The new Bookkeeper and Approval Manager templates had their Create buttons disabled in the agent picker, even though the connected Odoo instance exposed every model the templates need. Disabled in this UI means the template's requiredModels aren't all present in the synced schema — a useful affordance when Odoo Community is missing something Enterprise has, less useful when the synced schema is wrong because the syncer didn't probe the right models.
The root cause is a structural mismatch between two files. odoo-sync.ts probes a hand-curated list of ~37 models grouped into MODEL_CATEGORIES. odoo-template-validation.ts compares each template's requiredModels against the synced result. Templates can declare required models freely; the syncer doesn't know about them; everything not in the curated list reads as missing. A scan across all 22 Odoo templates revealed 14 templates with non-optional required models the syncer never probed — five of the six new v0.5.4 templates plus nine pre-existing ones (HR Analyst, Project Tracker, Manufacturing Planner, Recruitment Coordinator, POS Analyst, Marketing Analyst, Expense Auditor, Fleet Manager, Website Analyst). The pre-existing ones had been quietly broken since they shipped.
The fix has two halves. The first is to extend MODEL_CATEGORIES directly: HR gains hr.job, hr.leave, hr.attendance, hr.contract, hr.expense, hr.applicant and friends; Accounting gains account.tax and account.journal; and new categories land for Projects, Manufacturing, Point of Sale, Marketing, Fleet, and Website. The probed-models list grows from ~37 to ~80. The second is a drift-guard test: a unit test that loads every Odoo template, computes its requiredModels, and asserts every model is in MODEL_CATEGORIES (or flagged optional). A new template with a model the syncer doesn't probe is now a red CI run rather than a customer-disabled Create button. The matching follow-up commits raised the per-model retry test timeout to 15 s (the retry path now does ~80 models / 5 workers × 500 ms backoff ≈ 8 s of real wait, well over the old 5 s default) and switched the retry-counting from a single global callCount to a per-model Map, since 5-way concurrency shuffles the model→attempt mapping in ways the old global counter couldn't track.
The drift guard got generalised in the same session to cover all known Pinchy plugins, not just Odoo: every plugin that declares required-something now has its requirements walked against the syncer's probe list. The shape of the bug — declarations not matching the prober — exists across plugins; the test exists in one place.
Dep Bumps Ahead of v0.5.4
Eight low-risk production dep bumps landed in a single chore commit, ahead of v0.5.4's release window. The pattern — collect routine bumps into a single commit a few days before the release rather than landing them piecemeal during the release week — keeps the release-week diff focused on actual feature work. The matching astro bump (6.1.7 → 6.3.1) closed GHSA-xr5h-phrj-8vxv, which affects the docs build; the docs use Astro and pin separately from the marketing site. A small fix landed in CI: GHCR image pulls in the upgrade-simulation job now retry up to three times before being classified as a failure, because GHCR's rate-limit-then-200 pattern was making the simulation falsely flaky in the morning when most teams' nightly jobs run.
Day 85
The two main threads today share a shape. The version endpoint is the answer to can the customer verify what they got being closer to yes than it was yesterday. The odoo-sync drift guard is the answer to does this template work because someone manually traced the models being replaced by does this template work because the test refuses to let it ship if it doesn't. Both are the kind of fix that wouldn't have surfaced without a customer trying to do the thing the team had assumed worked. The week ahead will probably have more of these — v0.5.4's scope keeps expanding as the staging click-through finds the places where the assumptions and the implementation disagree.