fix(device-reconcile): replace uuid[] cast with text[] to avoid PG array parse failure#835
Conversation
…ray parse failure Production app pod logs (image 20260516-235210, 2026-05-17 00:10:06 UTC) repeatedly surfaced: [runs-queue] Run 197444 failed after retries: malformed array literal: "f7623d32-..." [runs-queue] Run 197426 failed after retries: malformed array literal: "f7623d32-..." Smoking gun: `device-reconcile.ts` was the only `::uuid[]` cast site in the entire server codebase (`rg "uuid\[\]"` returns one hit). The pin-self-heal UPDATE bound a `pgTextArray(matchingDeviceIds)` literal as a text parameter and then attempted a `::uuid[]` cast — postgres.js's extended-protocol path doesn't reliably re-parse the bound text as an array before applying the uuid[] cast, so PG sees the raw element bytes and fails with "malformed array literal: <uuid>". Fix: drop the uuid[] cast. Cast `device_worker_id::text` and compare against a `::text[]` instead. UUIDs are canonical lowercase, so text-form equality matches the uuid-form 1:1 with no semantic loss. Keeps the multi-device semantics intact (the column stays scalar uuid; this WHERE clause was always genuinely multi-valued). Fixes #781.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughDevice-pin reconciliation SQL WHERE clause updated from ChangesDevice Reconciliation Array Casting Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
pi review summary (verdict: comment / non-blocking):
Net: ship. |
* docs(agents): cross-repo dispatch pattern + e2e-before-merge gate - Owletto agents work in standalone ~/Code/owletto clone, not packages/owletto submodule (avoids inherited origin → wrong remote). - Don't pass "REPO: /absolute/path" in dispatch prompts — agents cd out of their isolation worktree. - Add e2e red→fix→green hard gate before opening bug-fix PRs. Bail if you can't reproduce. Motivated by the 2026-05-17 triage: both #781 and #782 agents hit the origin misconfig, and all three PRs (#833, #835, owletto#160) shipped without a reproducer. * docs(agents): clarify Pi reference per coderabbit review
Fixes #781.
Smoking gun
Production app pod logs (image `20260516-235210`, 2026-05-17 00:10:06 UTC):
```
[runs-queue] Run 197444 failed after retries: malformed array literal: "f7623d32-b589-4085-9504-edbf30925961"
[runs-queue] Run 197426 failed after retries: malformed array literal: "f7623d32-b589-4085-9504-edbf30925961"
```
`rg "uuid\[\]" packages db` returns exactly one hit across the entire backend — `packages/server/src/worker-api/device-reconcile.ts:68`, in the `reconcilePin` self-heal UPDATE. The error matches the shape of a bound text parameter (`pgTextArray(matchingDeviceIds)` → `{"f7623d..."}`) being passed through a `::uuid[]` cast that postgres.js's extended-protocol path doesn't re-parse as an array first.
(Note: the issue body pointed at commit 2ffccfb / #814 as a suspect, but that commit landed at 2026-05-17 05:11 UTC — after the crash at 00:10:06 UTC. The bug pre-dates it; `device-reconcile.ts:68` was introduced in #714 / 76fc6e9 on May 11.)
Rationale for the fix shape
Two options the issue suggested:
So the fix instead drops the `uuid[]` cast entirely and switches to `text[]` with `device_worker_id::text` on the left side. UUIDs are canonical lowercase in Postgres, so text equality matches the uuid form 1:1 — no semantic change, zero array-cast surface area.
`SET device_worker_id = ${target}::uuid` and the `IS DISTINCT FROM` clause on line 67 are left as-is — they bind a single uuid (or NULL), which the simple `::uuid` cast handles fine; only the `uuid[]` path was broken.
Test plan
No regression test added: would require new device_workers + connection + connector_definitions fixtures (none exist for this path) and the vitest suite isn't wired into CI. The fix is single-line and self-contained; pi review is the right gate.
Summary by CodeRabbit