fix(migrations): repair pocketbase v0.40 clean bootstrap - #1371
Merged
Conversation
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.
Contributor
There was a problem hiding this comment.
🟡 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
OnTerminatehook errors by returningte.Next()in Temporal worker and mobile-runner lifecycle hooks. - Update the
marketplace_itemsviewQueryto addGROUP BYin 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.27module 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The first deployment after PocketBase v0.40.3 failed during the webapp build:
credimi migrate upappeared successful but only committed PocketBase system migrations. The application migration failure was being masked by two customOnTerminatehooks returningnilinstead of propagatinge.Next().Once exposed, the actual failure was:
PocketBase v0.40 validates view queries more strictly. The marketplace query used aggregate
LEFT JOINbranches withoutGROUP 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
Verification
featuresexists; 39 collections created.bun run build, 41s).go test -tags=unit ./pkg/...passes: 37 packages, 1 package without tests.make lintpasses: vet, module verification, vulnerability scan, golangci-lint (0 issues).git diff --checkpasses.