Skip to content

fix(migrations): repair pocketbase v0.40 clean bootstrap - #1371

Merged
puria merged 2 commits into
mainfrom
fix/docker-go-1-27
Sep 10, 2026
Merged

fix(migrations): repair pocketbase v0.40 clean bootstrap#1371
puria merged 2 commits into
mainfrom
fix/docker-go-1-27

Conversation

@puria

@puria puria commented Sep 10, 2026

Copy link
Copy Markdown
Member

Problem

The first deployment after PocketBase v0.40.3 failed during the webapp build:

SQLITE_ERROR: no such table: features

credimi migrate up appeared successful but only committed PocketBase system migrations. The application migration failure was being masked by two custom OnTerminate hooks returning nil instead of propagating e.Next().

Once exposed, the actual failure was:

failed to apply migration 1759326382_updated_marketplace_items.js:
viewQuery: Invalid query - the query could return records with empty or invalid ids

PocketBase v0.40 validates view queries more strictly. The marketplace query used aggregate LEFT JOIN branches without GROUP BY, producing one NULL-id aggregate row when source collections were empty. Its custom-checks branch also had the UNION columns in the wrong order.

Change

  • Propagate terminate-event errors through the mobile-runner lifecycle hook and worker hook.
  • Add grouping to the credential-issuer and verifier aggregate branches.
  • Correct the custom-checks UNION column order in both migration directions.

Verification

  • Clean Debian-container migration: all 313 migrations apply; features exists; 39 collections created.
  • Webapp build against the clean migrated database passes (bun run build, 41s).
  • go test -tags=unit ./pkg/... passes: 37 packages, 1 package without tests.
  • make lint passes: vet, module verification, vulnerability scan, golangci-lint (0 issues).
  • git diff --check passes.

reason:
PR #1366 bumped go.mod to require go >= 1.27 but left the Dockerfile
builder on golang:1.26-alpine, so production deploys fail at
go mod download with GOTOOLCHAIN=local.

prompt:
Align the Docker builder image and mise go pin with the go.mod
toolchain requirement.
reason:
PocketBase v0.40 masked migration errors through terminate hooks, and
its stricter view validation rejected the marketplace query with empty
aggregate rows during a clean Docker build.

prompt:
Propagate termination errors and make the marketplace migration valid
for PocketBase v0.40.
Copilot AI lite review requested due to automatic review settings September 10, 2026 17:12
@puria
puria merged commit 14fbe82 into main Sep 10, 2026
9 checks passed
@puria
puria deleted the fix/docker-go-1-27 branch September 10, 2026 17:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The updated migration introduces a broken rollback UNION column alignment and still has an incorrect LEFT JOIN (missing ON) that will produce wrong children results and can cause serious query blow-ups.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR fixes a PocketBase v0.40.x clean-bootstrap migration failure by (1) ensuring terminate hooks don’t mask migration errors and (2) adjusting the marketplace_items view query to satisfy PocketBase’s stricter view validation.

Changes:

  • Propagate OnTerminate hook errors by returning te.Next() in Temporal worker and mobile-runner lifecycle hooks.
  • Update the marketplace_items viewQuery to add GROUP BY in aggregate branches and adjust UNION column ordering (intended for stricter PB v0.40 view validation).
  • Bump build/toolchain Go versions to align with the repository’s go 1.27 module directive.
File summaries
File Description
pkg/workflowengine/hooks/hook.go Propagates termination hook errors instead of returning nil.
pkg/internal/pb/mobile_runner_lifecycle.go Propagates termination hook errors instead of returning nil.
pb_migrations/1759326382_updated_marketplace_items.js Updates marketplace_items view SQL (grouping + UNION ordering changes).
Dockerfile Updates builder image to Go 1.27.
.mise.toml Updates local toolchain Go version to 1.27.0.
Review details

Files not reviewed (1)

  • pb_migrations/1759326382_updated_marketplace_items.js: Generated file
  • Files reviewed: 4/5 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// update collection data
unmarshal({
"viewQuery": "SELECT\n item.id,\n item.type,\n item.name,\n item.description,\n item.updated,\n item.avatar,\n item.avatar_url,\n item.organization_id,\n item.children,\n o.name AS organization_name\n\nFROM (\n\n SELECT\n w.id AS id,\n 'wallets' AS type,\n w.name AS name,\n w.description AS description,\n w.updated AS updated,\n CASE \n WHEN w.logo IS NOT NULL AND w.logo != '' THEN JSON_OBJECT(\n 'id', w.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'wallets'), \n 'collectionName', 'wallets', \n 'image_file', w.logo\n )\n ELSE NULL\n END AS avatar,\n w.logo_url AS avatar_url,\n w.owner AS organization_id,\n NULL as children\n FROM wallets w\n WHERE w.name IS NOT NULL AND w.published = true\n \n UNION ALL\n\n SELECT\n ci.id AS id,\n 'credential_issuers' AS type,\n ci.name AS name,\n ci.description AS description,\n ci.updated AS updated,\n NULL AS avatar,\n ci.logo_url AS avatar_url,\n ci.owner AS organization_id,\n COALESCE(\n json_group_array(\n json_object(\n 'id', cred.id,\n 'name', cred.display_name\n )\n ),\n '[]'\n ) as children\n FROM credential_issuers ci\n LEFT JOIN (\n SELECT *\n FROM credentials\n WHERE published = 1\n ORDER BY name\n ) cred\n ON cred.credential_issuer = ci.id\n WHERE ci.name IS NOT NULL AND ci.published = true\n\n UNION ALL\n\n SELECT\n cr.id AS id,\n 'credentials' AS type,\n COALESCE(NULLIF(cr.display_name, ''), cr.name) AS name,\n NULL AS description,\n cr.updated AS updated,\n NULL AS avatar,\n cr.logo AS avatar_url,\n cr.owner AS organization_id,\n NULL as children\n FROM credentials cr\n JOIN credential_issuers AS i ON cr.credential_issuer = i.id\n WHERE cr.name IS NOT NULL AND cr.published = true AND i.published = true\n\n UNION ALL\n\n SELECT\n cc.id AS id,\n cc.name AS name,\n cc.description AS description,\n cc.updated AS updated,\n JSON_OBJECT(\n 'id', cc.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'custom_checks'), \n 'collectionName', 'custom_checks', \n 'image_file', cc.logo\n ) AS avatar,\n NULL AS avatar_url,\n 'custom_checks' AS type, \n cc.owner AS organization_id,\n NULL as children\n FROM custom_checks cc\n WHERE cc.name IS NOT NULL AND cc.public = true\n\n UNION ALL\n\n SELECT\n vr.id AS id,\n 'verifiers' AS type, \n vr.name AS name,\n vr.description AS description,\n vr.updated AS updated,\n JSON_OBJECT(\n 'id', vr.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'verifiers'), \n 'collectionName', 'verifiers', \n 'image_file', vr.logo\n ) AS avatar,\n NULL AS avatar_url,\n vr.owner AS organization_id,\n COALESCE(\n json_group_array(\n json_object(\n 'id', vuc.id,\n 'name', vuc.name\n )\n ),\n '[]'\n ) as children\n FROM verifiers as vr\n LEFT JOIN (\n SELECT *\n FROM use_cases_verifications\n WHERE published = 1\n ORDER BY name\n ) vuc\n WHERE vr.name IS NOT NULL AND vr.published = true\n\n UNION ALL\n\n SELECT\n vuc.id AS id,\n 'use_cases_verifications' as type,\n vuc.name AS name,\n vuc.description AS description,\n vuc.updated as updated,\n NULL as avatar,\n NULL as avatar_url,\n vuc.owner as organization_id,\n NULL as children\n FROM use_cases_verifications as vuc\n JOIN verifiers as v ON vuc.verifier = v.id\n WHERE vuc.published = true AND v.published = true\n\n) AS item\n\nLEFT JOIN organizations o ON item.organization_id = o.id;"
"viewQuery": "SELECT\n item.id,\n item.type,\n item.name,\n item.description,\n item.updated,\n item.avatar,\n item.avatar_url,\n item.organization_id,\n item.children,\n o.name AS organization_name\n\nFROM (\n\n SELECT\n w.id AS id,\n 'wallets' AS type,\n w.name AS name,\n w.description AS description,\n w.updated AS updated,\n CASE \n WHEN w.logo IS NOT NULL AND w.logo != '' THEN JSON_OBJECT(\n 'id', w.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'wallets'), \n 'collectionName', 'wallets', \n 'image_file', w.logo\n )\n ELSE NULL\n END AS avatar,\n w.logo_url AS avatar_url,\n w.owner AS organization_id,\n NULL as children\n FROM wallets w\n WHERE w.name IS NOT NULL AND w.published = true\n \n UNION ALL\n\n SELECT\n ci.id AS id,\n 'credential_issuers' AS type,\n ci.name AS name,\n ci.description AS description,\n ci.updated AS updated,\n NULL AS avatar,\n ci.logo_url AS avatar_url,\n ci.owner AS organization_id,\n COALESCE(\n json_group_array(\n json_object(\n 'id', cred.id,\n 'name', cred.display_name\n )\n ),\n '[]'\n ) as children\n FROM credential_issuers ci\n LEFT JOIN (\n SELECT *\n FROM credentials\n WHERE published = 1\n ORDER BY name\n ) cred\n ON cred.credential_issuer = ci.id\n WHERE ci.name IS NOT NULL AND ci.published = true\n GROUP BY ci.id, ci.name, ci.description, ci.updated, ci.logo_url, ci.owner\n\n UNION ALL\n\n SELECT\n cr.id AS id,\n 'credentials' AS type,\n COALESCE(NULLIF(cr.display_name, ''), cr.name) AS name,\n NULL AS description,\n cr.updated AS updated,\n NULL AS avatar,\n cr.logo AS avatar_url,\n cr.owner AS organization_id,\n NULL as children\n FROM credentials cr\n JOIN credential_issuers AS i ON cr.credential_issuer = i.id\n WHERE cr.name IS NOT NULL AND cr.published = true AND i.published = true\n\n UNION ALL\n\n SELECT\n cc.id AS id,\n 'custom_checks' AS type,\n cc.name AS name,\n cc.description AS description,\n cc.updated AS updated,\n JSON_OBJECT(\n 'id', cc.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'custom_checks'), \n 'collectionName', 'custom_checks', \n 'image_file', cc.logo\n ) AS avatar,\n NULL AS avatar_url,\n cc.owner AS organization_id,\n NULL as children\n FROM custom_checks cc\n WHERE cc.name IS NOT NULL AND cc.public = true\n\n UNION ALL\n\n SELECT\n vr.id AS id,\n 'verifiers' AS type, \n vr.name AS name,\n vr.description AS description,\n vr.updated AS updated,\n JSON_OBJECT(\n 'id', vr.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'verifiers'), \n 'collectionName', 'verifiers', \n 'image_file', vr.logo\n ) AS avatar,\n NULL AS avatar_url,\n vr.owner AS organization_id,\n COALESCE(\n json_group_array(\n json_object(\n 'id', vuc.id,\n 'name', vuc.name\n )\n ),\n '[]'\n ) as children\n FROM verifiers as vr\n LEFT JOIN (\n SELECT *\n FROM use_cases_verifications\n WHERE published = 1\n ORDER BY name\n ) vuc\n WHERE vr.name IS NOT NULL AND vr.published = true\n GROUP BY vr.id, vr.name, vr.description, vr.updated, vr.logo, vr.owner\n\n UNION ALL\n\n SELECT\n vuc.id AS id,\n 'use_cases_verifications' as type,\n vuc.name AS name,\n vuc.description AS description,\n vuc.updated as updated,\n NULL as avatar,\n NULL as avatar_url,\n vuc.owner as organization_id,\n NULL as children\n FROM use_cases_verifications as vuc\n JOIN verifiers as v ON vuc.verifier = v.id\n WHERE vuc.published = true AND v.published = true\n\n) AS item\n\nLEFT JOIN organizations o ON item.organization_id = o.id;"
Comment on lines 45 to 48
// update collection data
unmarshal({
"viewQuery": "SELECT\n item.id,\n item.type,\n item.name,\n item.description,\n item.updated,\n item.avatar,\n item.avatar_url,\n item.organization_id,\n o.name AS organization_name\nFROM (\n SELECT\n w.id AS id,\n w.name AS name,\n w.description AS description,\n w.updated AS updated,\n CASE \n WHEN w.logo IS NOT NULL AND w.logo != '' THEN JSON_OBJECT(\n 'id', w.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'wallets'), \n 'collectionName', 'wallets', \n 'image_file', w.logo\n )\n ELSE NULL\n END AS avatar,\n w.logo_url AS avatar_url,\n 'wallets' AS type,\n w.owner AS organization_id\n FROM wallets w\n WHERE w.name IS NOT NULL AND w.published = true\n UNION ALL\n SELECT\n ci.id AS id,\n ci.name AS name,\n ci.description AS description,\n ci.updated AS updated,\n NULL AS avatar,\n ci.logo_url AS avatar_url,\n 'credential_issuers' AS type,\n ci.owner AS organization_id\n FROM credential_issuers ci\n WHERE ci.name IS NOT NULL AND ci.published = true\n\n UNION ALL\n\n SELECT\n cr.id AS id,\n COALESCE(NULLIF(cr.display_name, ''), cr.name) AS name,\n NULL AS description,\n cr.updated AS updated,\n NULL AS avatar,\n cr.logo AS avatar_url,\n 'credentials' AS type,\n cr.owner AS organization_id\n FROM credentials cr\n JOIN credential_issuers AS i ON cr.credential_issuer = i.id\n WHERE cr.name IS NOT NULL AND cr.published = true AND i.published = true\n\n UNION ALL\n\n SELECT\n cc.id AS id,\n cc.name AS name,\n cc.description AS description,\n cc.updated AS updated,\n JSON_OBJECT(\n 'id', cc.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'custom_checks'), \n 'collectionName', 'custom_checks', \n 'image_file', cc.logo\n ) AS avatar,\n NULL AS avatar_url,\n 'custom_checks' AS type, \n cc.owner AS organization_id\n FROM custom_checks cc\n WHERE cc.name IS NOT NULL AND cc.public = true\n\n UNION ALL\n\n SELECT\n vr.id AS id,\n vr.name AS name,\n vr.description AS description,\n vr.updated AS updated,\n JSON_OBJECT(\n 'id', vr.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'verifiers'), \n 'collectionName', 'verifiers', \n 'image_file', vr.logo\n ) AS avatar,\n NULL AS avatar_url,\n 'verifiers' AS type, \n vr.owner AS organization_id\n FROM verifiers as vr\n WHERE vr.name IS NOT NULL AND vr.published = true\n\n UNION ALL\n\n SELECT\n vuc.id AS id,\n vuc.name AS name,\n vuc.description AS description,\n vuc.updated as updated,\n NULL as avatar,\n NULL as avatar_url,\n 'use_cases_verifications' as type,\n vuc.owner as organization_id\n FROM use_cases_verifications as vuc\n JOIN verifiers as v ON vuc.verifier = v.id\n WHERE vuc.published = true AND v.published = true\n) AS item\n\nLEFT JOIN organizations o ON item.organization_id = o.id;"
"viewQuery": "SELECT\n item.id,\n item.type,\n item.name,\n item.description,\n item.updated,\n item.avatar,\n item.avatar_url,\n item.organization_id,\n o.name AS organization_name\nFROM (\n SELECT\n w.id AS id,\n w.name AS name,\n w.description AS description,\n w.updated AS updated,\n CASE \n WHEN w.logo IS NOT NULL AND w.logo != '' THEN JSON_OBJECT(\n 'id', w.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'wallets'), \n 'collectionName', 'wallets', \n 'image_file', w.logo\n )\n ELSE NULL\n END AS avatar,\n w.logo_url AS avatar_url,\n 'wallets' AS type,\n w.owner AS organization_id\n FROM wallets w\n WHERE w.name IS NOT NULL AND w.published = true\n UNION ALL\n SELECT\n ci.id AS id,\n ci.name AS name,\n ci.description AS description,\n ci.updated AS updated,\n NULL AS avatar,\n ci.logo_url AS avatar_url,\n 'credential_issuers' AS type,\n ci.owner AS organization_id\n FROM credential_issuers ci\n WHERE ci.name IS NOT NULL AND ci.published = true\n\n UNION ALL\n\n SELECT\n cr.id AS id,\n COALESCE(NULLIF(cr.display_name, ''), cr.name) AS name,\n NULL AS description,\n cr.updated AS updated,\n NULL AS avatar,\n cr.logo AS avatar_url,\n 'credentials' AS type,\n cr.owner AS organization_id\n FROM credentials cr\n JOIN credential_issuers AS i ON cr.credential_issuer = i.id\n WHERE cr.name IS NOT NULL AND cr.published = true AND i.published = true\n\n UNION ALL\n\n SELECT\n cc.id AS id,\n 'custom_checks' AS type,\n cc.name AS name,\n cc.description AS description,\n cc.updated AS updated,\n JSON_OBJECT(\n 'id', cc.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'custom_checks'), \n 'collectionName', 'custom_checks', \n 'image_file', cc.logo\n ) AS avatar,\n NULL AS avatar_url,\n cc.owner AS organization_id\n FROM custom_checks cc\n WHERE cc.name IS NOT NULL AND cc.public = true\n\n UNION ALL\n\n SELECT\n vr.id AS id,\n vr.name AS name,\n vr.description AS description,\n vr.updated AS updated,\n JSON_OBJECT(\n 'id', vr.id,\n 'collectionId', (SELECT id FROM _collections WHERE name = 'verifiers'), \n 'collectionName', 'verifiers', \n 'image_file', vr.logo\n ) AS avatar,\n NULL AS avatar_url,\n 'verifiers' AS type, \n vr.owner AS organization_id\n FROM verifiers as vr\n WHERE vr.name IS NOT NULL AND vr.published = true\n\n UNION ALL\n\n SELECT\n vuc.id AS id,\n vuc.name AS name,\n vuc.description AS description,\n vuc.updated as updated,\n NULL as avatar,\n NULL as avatar_url,\n 'use_cases_verifications' as type,\n vuc.owner as organization_id\n FROM use_cases_verifications as vuc\n JOIN verifiers as v ON vuc.verifier = v.id\n WHERE vuc.published = true AND v.published = true\n) AS item\n\nLEFT JOIN organizations o ON item.organization_id = o.id;"
}, collection)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants