Troubleshooting Guide
When you run into issues, read this document for known problems and solutions.
Quick Index
Tip: know the exact error string but not the phase? Use the
constructive-error-indexskill where available — a flatsymptom string → cause → fix pointerlookup (gotcha CODE / this guide's section) that routes you straight to the authoritative entry.
- General
- Docker Postgres issues
- Phase 1
- GraphQL Server not responding
- GraphQL Server returns HTML "Not Found"
- Agent stuck on nohup + curl verification (hang)
- Post-Provision (Email Services)
- Mailpit not running
- Admin GraphQL Server (3002) hangs on startup
- send-email-link not sending emails
- "Missing site configuration for email" error
- job-service not processing jobs
- Phase 2.1
pgpm initnon-interactive mode fails
- Phase 2.3
__dirnameundefined in ESM scripts- Workaround schema name has hash (query real schema name with psql first)
constructBlueprintfailsNOT_FOUND (memberships_module)(AuthzEntityMembership on an org-less app — use AuthzDirectOwner/AuthzAllowAll)
- Phase 2.4 (Optional - skip in most cases, boilerplate includes codegen)
- SDK build missing dependencies
makagemissingtsconfig.esm.json/README.mdpgpm initfails in non-packages directory- Recommend skipping CLI by default (generate schema + sdk only)
- Phase 2.5 (Boilerplate only)
pgpm init -whangs (waiting for input)- Next.js 500 after start (missing
@sdk/*/ generated output)
- Phase 3 (Per-DB integration + business UI)
- updateUser returns 200 but does not persist (users-table UPDATE-policy gap — apply the createSecureTableProvision self_update step)
- Auth hook onSuccess cannot get password (mutationFn must return password)
- configure-app-sdk
headerstype error (must pass Record<string, string>, not a function) - Route structure (don't assume (authenticated) etc.; check template app/ structure first)
- Don't wrap page in AppShell again (layout already has AuthenticatedShell; see existing users/page.tsx)
- ORM/client delete missing select (must pass select explicitly)
- orderBy enum values missing (CREATED_AT_DESC / POSITION_ASC etc.; use generated schema values or add index first)
- No QueryClient set (SDK hooks vs app react-query instance; prefer generating SDK inside app)
- No redirect after login / no Sidebar entry (router.push in onSuccess, add links in sidebar)
- UI component import wrong (
@constructive-io/ui/*vs template@/components/ui/*) - Use Stack when template has it (do not create Dialog for CRUD — check
ls .../ui/stackfirst; see constructive-frontend / CRUD Stack) - SDK query result fields nullable (type errors in form/handler — use
?? ''or accept nullable type) - Invalid UUID error on create/update (relation field
isRequired: falsemissing) - Used confirm() or alert() for delete (must use template AlertDialog from
@/components/ui/alert-dialog) - Next.js cannot find
@<app>/sdk/dist/...(workspace path resolution) - Hooks argument error (mutation no input wrapper, query pass id directly; check generated hook @example / select vs selection.fields)
General: Docker Postgres issues
Problem
Postgres container is not running or connection fails.
Solution
# Check container status
docker ps | grep postgres
# If not running, start it
eval "$(pgpm env)"
pgpm docker start
# Verify connection
psql -h localhost -U postgres -c "SELECT 1;"
Phase 1: GraphQL Server not responding
Problem
curl returns 000 or hangs (connection failure / timeout).
Solution
- Ensure the database is deployed:
eval "$(pgpm env)"
psql -c "SELECT datname FROM pg_database WHERE datname = 'constructive';"
- If the database does not exist, deploy it:
cd /path/to/constructive-db
pgpm deploy --database constructive --createdb --yes --package constructive-services
pgpm deploy --database constructive --yes --package constructive-local
- Start the GraphQL server:
cd /path/to/constructive/graphql/server
PGDATABASE=constructive pnpm dev
- Wait for the server to be ready (first start may take 20–30 seconds):
sleep 25
- Health check with POST + Host header + JSON body (expect GraphQL data):
curl -s --connect-timeout 5 --max-time 10 \
-H "Host: auth.localhost:3000" \
-H "Content-Type: application/json" \
-X POST \
http://localhost:3000/graphql \
-d '{"query":"{ __typename }"}'
# Expected: {"data":{"__typename":"Query"}}
Phase 1: GraphQL Server returns HTML "Not Found"
Problem
GraphQL server process is running (lsof -i:3000 shows node) but the request returns an HTML page instead of a GraphQL response:
curl -H "Host: auth.localhost:3000" http://localhost:3000/graphql
# Returns HTML: <title>Not Found</title>
Cause
The Constructive database (constructive) is not deployed. The GraphQL server needs to connect to a deployed database at startup to route requests correctly.
Solution
- Check if the database exists:
eval "$(pgpm env)"
psql -c "SELECT datname FROM pg_database WHERE datname = 'constructive';"
- If the database does not exist or is empty, deploy it:
cd /path/to/constructive-db
eval "$(pgpm env)"
# Create and deploy database
dropdb --if-exists constructive
pgpm deploy --database constructive --createdb --yes --package constructive-services
pgpm deploy --database constructive --yes --package constructive-local
- Restart the GraphQL server:
# Kill current process
lsof -ti:3000 | xargs kill -9
# Restart
screen -dmS graphql bash -c 'cd /path/to/constructive/graphql/server && eval "$(pgpm env)" && PGDATABASE=constructive pnpm dev'
sleep 15
- Verify:
curl -s --connect-timeout 5 --max-time 10 -H "Host: auth.localhost:3000" -H "Content-Type: application/json" -X POST http://localhost:3000/graphql -d '{"query":"{ __typename }"}'
# Expected: {"data":{"__typename":"Query"}}
Verification
# Check database tables
psql -d constructive -c "SELECT COUNT(*) FROM metaschema_public.database;"
# Expected: number greater than 0
Phase 1: Agent stuck on nohup + curl verification (hang)
Problem
After running the following commands the Agent hangs with no response:
cd .../constructive/graphql/server && PGDATABASE=constructive nohup pnpm dev > server.log 2>&1 &
sleep 3
curl -s -o /dev/null -w "%{http_code}" http://api.localhost:3000/graphql
Cause
- curl has no timeout: When the server is not ready, curl waits indefinitely for a connection by default.
- sleep 3 is too short: GraphQL first start (compile, connect to DB) usually needs 20–30 seconds.
- nohup missing pgpm env: Without
eval "$(pgpm env)", the DB connection may fail. - api.localhost resolution: In some environments DNS resolution is slow or broken.
Solution (using screen)
# Run the following in the workspace root that contains the `constructive/` repo
screen -dmS graphql bash -c 'cd ./constructive/graphql/server && eval "$(pgpm env)" && PGDATABASE=constructive pnpm dev'
sleep 25
# Health check with localhost + Host header + POST + JSON
curl -s --connect-timeout 5 --max-time 10 \
-H "Host: auth.localhost:3000" \
-H "Content-Type: application/json" \
-X POST \
http://localhost:3000/graphql \
-d '{"query":"{ __typename }"}'
Key improvements:
| Improvement | Description |
|---|---|
--connect-timeout 5 |
Connection timeout 5s to avoid waiting forever |
--max-time 10 |
Total request timeout 10s |
-H "Host: auth.localhost:3000" + http://localhost:3000 |
Use localhost (bypass DNS) + correct Host header for routing |
sleep 25 |
Give server enough time to start (first start usually 20–30s) |
eval "$(pgpm env)" |
Ensure DB connection env vars are set |
Verification
# Check port
lsof -i:3000 | grep LISTEN
# View startup log
tail -20 ./constructive/graphql/server/server.log
# Expected: curl returns 405
Post-Provision: Mailpit not running
Problem
Mailpit container is not running or ports 1025/8025 are not accessible.
Solution
# Check if container exists
docker ps -a | grep mailpit
# If not created, create and start it
docker run -d --name mailpit -p 1025:1025 -p 8025:8025 axllent/mailpit
# If created but stopped
docker start mailpit
# Verify
curl -s http://localhost:8025 | head -5
Post-Provision: Admin GraphQL Server (3002) hangs on startup
Problem
After Admin server starts, port 3002 is not listening, process appears hung. Screen session exists but service is not responding.
Cause
constructive server without --origin parameter enters interactive mode, waiting for user input for CORS origin. In Agent/CI environments without TTY input, this causes infinite waiting.
Solution
Must add --origin "*" parameter to the command:
# ❌ Wrong - will hang waiting for input
constructive server --port 3002
# ✅ Correct - skip interactive prompt
constructive server --port 3002 --origin "*"
Full startup command:
screen -dmS admin-server bash -c '
eval "$(pgpm env)" && \
PGDATABASE=constructive \
API_ENABLE_SERVICES=true \
API_IS_PUBLIC=false \
API_ANON_ROLE=administrator \
API_ROLE_NAME=administrator \
API_EXPOSED_SCHEMAS=metaschema_public,services_public,constructive_auth_public \
API_META_SCHEMAS=metaschema_public,services_public,metaschema_modules_public,constructive_auth_public \
constructive server --port 3002 --origin "*"
'
Verification
# Wait for startup
sleep 10
# Check port
lsof -i:3002 | grep LISTEN
# Test GraphQL endpoint
curl -s http://localhost:3002/graphql -H "Content-Type: application/json" \
-d '{"query":"{ __typename }"}'
# Expected return: {"data":{"__typename":"Query"}}
Post-Provision: send-email-link not sending emails
Problem
Emails are not appearing in Mailpit UI after user signup or password reset.
Cause
- send-email-link is not running
- Admin GraphQL Server (port 3002) is not running
- job-service is not running
SEND_EMAIL_LINK_DRY_RUN=true(should befalse)
Solution
Step 1: Check all services are running:
# Check HTTP services
for port in 3002 8082; do
if lsof -i:$port | grep -q LISTEN; then
echo "✅ Port $port - Running"
else
echo "❌ Port $port - Not running"
fi
done
# Check job-service (no HTTP port)
if pgrep -f "knative-job-service" > /dev/null; then
echo "✅ job-service - Running"
else
echo "❌ job-service - Not running"
fi
Step 2: Check logs for errors:
tail -50 /tmp/send-email-link.log
tail -50 /tmp/job-service.log
tail -50 /tmp/admin-server.log
Step 3: Verify SEND_EMAIL_LINK_DRY_RUN is false:
If DRY_RUN is true, emails are logged but not sent. Restart send-email-link with SEND_EMAIL_LINK_DRY_RUN=false.
Post-Provision: "Missing site configuration for email" error
Problem
send-email-link logs show:
"Missing site configuration for email"
Cause
The database has no site domain configured. The provision flow creates API domains (e.g., api-xxx.localhost, the per-DB data host) but not site domains.
Solution
Add a site domain after provisioning:
-- Replace <your-db-name> with your database name
INSERT INTO services_public.domains (database_id, site_id, subdomain, domain)
SELECT
db.id,
s.id,
'<your-db-name>',
'localhost'
FROM metaschema_public.database db
JOIN services_public.sites s ON s.database_id = db.id
WHERE db.name = '<your-db-name>'
ON CONFLICT (subdomain, domain) DO NOTHING;
Verification
SELECT d.subdomain, d.domain
FROM services_public.domains d
JOIN services_public.sites s ON d.site_id = s.id
JOIN metaschema_public.database db ON d.database_id = db.id
WHERE db.name = '<your-db-name>';
-- Should return a row
Post-Provision: job-service not processing jobs
Problem
Jobs are being added to the queue but not processed. send-email-link never receives requests.
Solution
Step 1: Check job-service is running:
pgrep -f "knative-job-service" || echo "Not running!"
Step 2: Check logs:
tail -f /tmp/job-service.log
# Should see: "worker-0 connected and looking for jobs..."
Step 3: Verify environment variables:
The most important variable is INTERNAL_GATEWAY_DEVELOPMENT_MAP:
export INTERNAL_GATEWAY_DEVELOPMENT_MAP='{"send-email-link":"http://localhost:8082"}'
Step 4: Restart job-service if needed:
pkill -f "knative-job-service"
cd $CONSTRUCTIVE_PATH/jobs/knative-job-service
# Set all env vars...
nohup node dist/run.js > /tmp/job-service.log 2>&1 &
Phase 2.1: pgpm init non-interactive mode fails
Problem
Running pgpm init even with --no-tty or CI=true still throws error:
Error [ERR_USE_AFTER_CLOSE]: readline was closed
Cause
pgpm's inquirer dependency has issues in non-TTY environments. Must pass all required parameters along with --no-tty for it to work properly.
Solution
Pass all required parameters (including --repoName)
For workspace, must provide all parameters (note: --repoName is required):
pgpm init workspace --no-tty \
--name my-workspace \
--fullName "Your Name" \
--email "you@example.com" \
--username your-github-username \
--license MIT \
--repoName my-workspace
For module, must provide all parameters:
pgpm init --no-tty \
--moduleName my-module \
--moduleDesc "My module description" \
--fullName "Your Name" \
--email "you@example.com" \
--username your-github-username \
--repoName my-workspace \
--license MIT \
--access public \
--extensions "plpgsql,uuid-ossp"
Verification
ls my-workspace/
# Should see: pgpm.json, pnpm-workspace.yaml, package.json, packages/
Phase 2.2/2.3: pnpm install fails
Problem
pnpm install throws error: cannot find @constructive-io/* packages.
Solution
Ensure .npmrc is configured correctly:
cat > .npmrc << 'EOF'
@constructive-io:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
EOF
Ensure GITHUB_TOKEN environment variable is set.
General: Running services in background (recommended: screen)
Problem
After running pnpm dev or other server startup commands, Agent gets stuck waiting for command to complete, or process terminates after command ends.
Recommended: screen
Using screen allows the server to run persistently in the background, unaffected by terminal closure:
# Start GraphQL Server (port 3000)
screen -dmS graphql bash -c 'cd /path/to/constructive/graphql/server && eval "$(pgpm env)" && PGDATABASE=constructive pnpm dev'
# Start Next.js App (port 3081)
screen -dmS app bash -c 'cd /path/to/app && pnpm dev --port 3081'
# Wait for startup
sleep 10
# Verify
lsof -i:3000 | head -3 # GraphQL
lsof -i:3081 | head -3 # Next.js
screen common commands
# List all screen sessions
screen -ls
# Attach to specified session (view logs)
screen -r graphql
screen -r app
# Detach (without stopping service): Press Ctrl+A then D
# Stop specified session
screen -S graphql -X quit
screen -S app -X quit
# Stop service (by port)
lsof -ti:3000 | xargs kill # GraphQL
lsof -ti:3081 | xargs kill # Next.js
Quick start commands
# GraphQL Server
screen -dmS graphql bash -c 'cd /path/to/constructive/graphql/server && eval "$(pgpm env)" && PGDATABASE=constructive pnpm dev'
# Next.js App
screen -dmS app bash -c 'cd /path/to/app && pnpm dev --port 3081'
# Verify startup
sleep 10
# Add timeout to prevent hang; api endpoint needs Host header
curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w "%{http_code}" -H "Host: api.localhost:3000" http://127.0.0.1:3000/graphql # Expected 405
curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w "%{http_code}" http://127.0.0.1:3081 # Expected 200
Option comparison
| Method | Pros | Cons |
|---|---|---|
screen |
✅ Persistent, interactive log viewing | Requires screen installed |
& + log |
✅ Simple, no dependencies | May stop when terminal closes |
nohup |
✅ Won't stop when terminal closes | Inconvenient for real-time logs |
Verification
# Check port
lsof -i:3000 | grep LISTEN # GraphQL
lsof -i:3081 | grep LISTEN # Next.js
# Test API (add timeout to prevent hang)
curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w "%{http_code}" -H "Host: api.localhost:3000" http://127.0.0.1:3000/graphql # Expected 405
curl -s --connect-timeout 5 --max-time 10 -o /dev/null -w "%{http_code}" http://127.0.0.1:3081 # Expected 200
Phase 1: Postgres connection refused
Problem
When running psql or other database commands, throws error:
psql: error: connection to server at "localhost" (::1), port 5432 failed: Connection refused
Is the server running on that host and accepting TCP/IP connections?
Cause
pgpm docker needs to be run inside the constructive-db directory to start correctly.
Solution
# Enter constructive-db directory
cd /path/to/constructive-db
# Start docker
eval "$(pgpm env)"
pgpm docker start
# Verify connection
psql -c "SELECT 1;"
Verification
psql -c "SELECT 1;"
# Should return:
# ?column?
# ----------
# 1
Phase 1: role "administrator" does not exist
Problem
When running pgpm deploy, throws error:
role "administrator" does not exist
Cause
Database roles (such as administrator, authenticated, etc.) have not been created yet. Need to bootstrap these roles first.
Solution
Before deploying the database, run bootstrap command to create necessary roles:
eval "$(pgpm env)"
# 1. Bootstrap roles
pgpm admin-users bootstrap --yes
pgpm admin-users add --test --yes
# 2. Create database
createdb constructive
# 3. Deploy database
pgpm deploy --yes --database constructive --package constructive-local
Verification
psql -d constructive -c "SELECT rolname FROM pg_roles WHERE rolname = 'administrator';"
# Should return administrator role
Phase 2.3: SDK package not found or wrong imports
Problem
pnpm install throws 404 errors for SDK packages, or imports fail.
Solution
Use the correct SDK packages from the npm registry:
| Package | Use Case |
|---|---|
@constructive-io/sdk |
Node.js and browser (provision, CLI, React, Next.js) |
@constructive-io/graphql-codegen |
SDK code generation |
See the constructive-data-modeling skill for setup details and the constructive-codegen skill for codegen.
Verification
pnpm install
pnpm build
# No 404 or import errors
Phase 2.3: SignUp return type missing ok/errors fields
Problem
Compilation error:
Object literal may only specify known properties, and 'ok' does not exist in type 'SignUpPayloadSelect'.
Cause
SDK's signUp mutation returns SignUpPayloadSelect, which has structure { result: { select: SignUpRecordSelect } }, without ok and errors fields.
Solution
Use the correct select structure:
// ❌ Wrong
const signUpResult = await authDb.mutation
.signUp(
{ input: { email, password } },
{ select: { ok: true, errors: true } } // These fields don't exist
)
.execute();
// ✅ Correct
const signUpResult = await authDb.mutation
.signUp(
{ input: { email, password } },
{ select: { result: { select: { userId: true } } } }
)
.execute();
// Check result
if (!signUpResult.ok || !signUpResult.data?.signUp?.result) {
console.log('Sign up failed or user already exists');
}
Verification
pnpm build
# Should compile successfully
Phase 2.3: setHeaders method does not exist
Problem
Compilation error:
Property 'setHeaders' does not exist on type '{ orgGetManagersRecord: ...; database: ...; ... }'.
Cause
The ORM object returned by createClient does not have setHeaders method. This method is on the adapter, not the client.
The db.setHeaders() example in Skill documentation is idealized, actual SDK structure is different.
Solution
Pass headers when creating the client:
// ❌ Wrong
const publicDb = createPublicClient({
endpoint: 'http://api.localhost:3000/graphql',
});
publicDb.setHeaders({ Authorization: `Bearer ${accessToken}` }); // Does not exist
// ✅ Correct
const publicDb = createPublicClient({
endpoint: 'http://api.localhost:3000/graphql',
headers: { Authorization: `Bearer ${accessToken}` },
});
Verification
pnpm build
# Should compile successfully
Phase 2.3: secureTableProvision/field/relationProvision not in admin SDK
Problem
Compilation error:
Property 'secureTableProvision' does not exist on type ...
Property 'field' does not exist on type ...
Cause
Schema operations (secureTableProvision, field, relationProvision, table) are in the public SDK, not the admin SDK. The admin-<db>.localhost endpoint name refers to the GraphQL endpoint, not the admin SDK client.
Solution
Use the public SDK client for all schema operations. See the constructive-data-modeling skill for client setup and table / field usage examples.
Verification
pnpm build
# Should compile successfully
Phase 2.3: constructBlueprint fails with NOT_FOUND (memberships_module)
Problem
constructBlueprint returns status: failed with errorDetails: "NOT_FOUND (memberships_module)", and the table is never created (not a silent 0-row — a hard abort).
Cause
A table declared policies: [{ $type: 'AuthzEntityMembership', data: { entity_field: 'entity_id', membership_type: 2 }, … }], but the app was provisioned with the auth:hardened preset (or a basic auth module list), which has no org-scoped memberships modules. The org-scoped membership SPRT that AuthzEntityMembership resolves does not exist, so the construct aborts.
Solution
Default a basic (org-less) app to owner-scoped policies, not entity-membership ones:
// ✅ Each user owns their rows (default for a basic app)
nodes: ['DataId', 'DataDirectOwner', { $type: 'DataTimestamps', data: { include_id: false } }],
use_rls: true,
policies: [{ $type: 'AuthzDirectOwner', privileges: ['select','insert','update','delete'], permissive: true, data: { entity_field: 'owner_id' } }],
// ✅ App-wide shared pool (no ownership)
nodes: ['DataId', { $type: 'DataTimestamps', data: { include_id: false } }],
use_rls: true,
policies: [{ $type: 'AuthzAllowAll', privileges: ['select','insert','update','delete'], permissive: true }],
// ❌ Aborts on auth:hardened — only valid once the `b2b:storage` org modules are provisioned
policies: [{ $type: 'AuthzEntityMembership', data: { entity_field: 'entity_id', membership_type: 2 }, … }],
The schemas/core.ts template already ships the owner-scoped default. Remember the FK prereq: owner_id FKs to the per-tenant users table, so sign the authed user up via the TENANT endpoint (auth-<sub>.localhost), not base auth.localhost. See gotchas RLS-POLICY-001.
Verification
# Re-run provision; the construct should report status: completed and create the table.
pnpm run provision
Phase 3: updateUser returns 200 but does not persist (silent no-op)
Problem
Calling updateUser (profile / account-settings) succeeds (HTTP 200, no error), but the username / display_name / profile_picture change is not saved — re-querying the user shows the old values.
Cause
The dynamically-provisioned per-tenant users table has RLS enabled and a column UPDATE grant to authenticated, but the dynamic provisioner emits only an auth_sel SELECT policy and no UPDATE policy. RLS therefore rejects the update and 0 rows change — silently. Deterministic across tenants; users is module-owned, so this cannot be fixed in the blueprint.
Solution
Apply the users-table self-update policy as a control-plane step (the provision.ts template already runs it) — createSecureTableProvision on http://modules.localhost:3000/graphql with the provisioning sudo token:
await modulesClient.secureTableProvision.create({
data: {
databaseId, // tenant db uuid
schemaId, // metaschema_public.schema WHERE name='users_public'
tableId, // metaschema_public.table WHERE name='users'
tableName: 'users',
useRls: true,
policies: [{ $type: 'AuthzDirectOwner', permissive: true, privileges: ['update'],
policy_name: 'self_update', data: { entity_field: 'id' } }] as unknown as Record<string, unknown>,
},
select: { id: true },
}).unwrap();
This emits auth_upd_self_update (FOR UPDATE TO authenticated USING id = jwt_public.current_user_id()) and updateUser persists. See gotchas RLS-USERS-UPDATE-001. (Platform gap, flagged upstream; control-plane step is the app-side reconciliation.)
Verification
# Expect BOTH auth_sel and auth_upd_self_update on the users table:
psql "$PGDATABASE" -c "SELECT polname FROM pg_policy WHERE polrelid =
(SELECT oid FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace
WHERE c.relname='users' AND n.nspname LIKE '%users-public' LIMIT 1);"
# Then call updateUser and re-query the user — the change should persist.
Phase 2.3: table.findOne parameter error (where vs id)
Problem
Compilation error:
Object literal may only specify known properties, and 'where' does not exist in type '{ id: string; select: TableSelect; }'.
Cause
The findOne method accepts the id parameter directly, unlike findMany which uses a where parameter.
Solution
Use the correct findOne parameters:
// ❌ Wrong - findOne has no where parameter
const result = await db.table.findOne({
where: { id: tableId },
select: { id: true, name: true },
}).execute();
// ✅ Correct - pass id directly
const result = await db.table.findOne({
id: tableId,
select: { id: true, name: true },
}).execute();
findOne vs findMany parameter comparison
| Method | Parameters |
|---|---|
findOne |
{ id: string, select: {...} } |
findMany |
{ where: {...}, select: {...}, first?: number } |
findFirst |
{ where: {...}, select: {...} } |
Verification
pnpm build
# Should compile successfully
Phase 2.4: SDK build missing dependencies
Problem
Building the generated SDK throws an error:
Cannot find module '@tanstack/react-query' or its corresponding type declarations.
Cannot find module '@constructive-io/graphql-types' or its corresponding type declarations.
Cannot find module 'graphql' or its corresponding type declarations.
Cause
The generated SDK code depends on these packages, but they are not declared in package.json.
Solution
Add the missing dependencies:
cd sdk/sdk
# Add dependencies
cat package.json | jq '
.dependencies = {
"@tanstack/react-query": "^5.0.0",
"@constructive-io/graphql-types": "link:/path/to/constructive/graphql/types/dist",
"@0no-co/graphql.web": "^1.0.0",
"gql-ast": "^3.0.0",
"graphql": "^16.0.0"
}
' > package.json.tmp && mv package.json.tmp package.json
pnpm install
Verification
pnpm build
# Should build successfully
Phase 2.4: makage build missing tsconfig.esm.json
Problem
Running makage build throws an error:
error TS5058: The specified path does not exist: 'tsconfig.esm.json'.
Cause
makage requires tsconfig.esm.json to build ESM output.
Solution
Create tsconfig.esm.json:
cat > tsconfig.esm.json << 'EOF'
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "dist/esm",
"module": "ES2022",
"moduleResolution": "bundler"
}
}
EOF
Verification
pnpm build
# Should build successfully, generating dist/ and dist/esm/ directories
Phase 2.4: makage build missing README.md
Problem
Running makage build throws an error:
ENOENT: no such file or directory, stat 'README.md'
Cause
makage tries to copy README.md to the dist directory, and throws an error if the file does not exist.
Solution
Create README.md:
echo "# Package Name" > README.md
Verification
pnpm build
# Should build successfully
Phase 2.3/2.4: __dirname undefined in ESM scripts
Problem
Running TypeScript scripts in ESM mode ("type": "module") throws an error:
ReferenceError: __dirname is not defined
Cause
__dirname and __filename are CommonJS global variables that do not exist in ESM mode.
Solution
Use import.meta.url instead:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Now you can use __dirname normally
const outputDir = path.resolve(__dirname, '../output');
Verification
pnpm tsx script.ts
# Should run normally, no more __dirname error
Phase 2.3: TypeScript select field does not exist (e.g. schemaName not in type)
Problem
When writing provision scripts, TypeScript reports errors like:
Property 'schemaName' does not exist on type ...Object literal may only specify known properties ...
Cause
The SDK's select types are strict: you can only select fields that actually exist in the schema and are exposed by codegen. Many “seemingly reasonable” field names (e.g., schemaName) do not exist in that entity's select type.
Solution
- Start with minimal select (only get
id,name, and other fields you know exist), get the script working, then add fields incrementally - Let types be your guide: Let your editor/TS suggestions drive field selection, rather than guessing field names
- If you just need to “confirm creation succeeded”, usually
id: trueis sufficient
Verification
pnpm build
# TypeScript compiles successfully
Phase 2.3: Workaround SQL schema name contains hash
Problem
When applying workarounds (like fix-membership-defaults) an error is thrown:
ERROR: schema "<db>-user-identifiers-public" does not exist
Cause
In Per-DB mode, schema names include a hash suffix, like <db>-a65661ed-user-identifiers-public, not simply <db>-user-identifiers-public (<db> is your database name).
Solution
The <db>-user-identifiers-public in the documentation is a template format. Actual Per-DB schema names include a hash, so you must first run the psql query below to get the real schema name, then execute ALTER/UPDATE.
First query the correct schema name:
eval "$(pgpm env)"
# Find schemas containing the database name
psql constructive -t -c "SELECT table_schema FROM information_schema.tables WHERE table_name = 'emails' AND table_schema LIKE '%<dbName>%';"
# Example output: <db>-a65661ed-user-identifiers-public
Then use the correct schema name to execute SQL:
# Replace <schema> with the actual name from the query
psql constructive -c 'ALTER TABLE "<schema>".emails ALTER COLUMN is_verified SET DEFAULT true;'
psql constructive -c 'UPDATE "<schema>".emails SET is_verified = true;'
Or use a wildcard query to get it automatically:
# Automatically find and apply
SCHEMA=$(psql constructive -t -c "SELECT table_schema FROM information_schema.tables WHERE table_name = 'emails' AND table_schema LIKE '%<db>%' LIMIT 1;" | tr -d ' ')
psql constructive -c "ALTER TABLE \"$SCHEMA\".emails ALTER COLUMN is_verified SET DEFAULT true;"
psql constructive -c "UPDATE \"$SCHEMA\".emails SET is_verified = true;"
Verification
psql constructive -c "SELECT is_verified FROM \"$SCHEMA\".emails;"
# Should return true
Phase 2.4: pgpm init fails in non-packages directory
Problem
Running pgpm init in non-standard directories like sdk/ throws an error:
Error: You must be inside the workspace root, a parent directory of modules (like 'packages/'), or inside one of the workspace packages
Cause
pgpm init only recognizes packages/ and extensions/ as module directories. Custom directories (like sdk/) are not automatically recognized.
Solution
Option A: Manually create directories and files
# 1. Create directory structure
mkdir -p sdk/my-package/src
# 2. Manually create package.json
cat > sdk/my-package/package.json << 'EOF'
{
"name": "@myapp/my-package",
"version": "0.0.1",
"main": "index.js",
"module": "esm/index.js",
"types": "index.d.ts",
"publishConfig": {
"access": "public",
"directory": "dist"
},
"scripts": {
"clean": "makage clean",
"build": "makage build"
},
"devDependencies": {
"makage": "^0.1.10"
}
}
EOF
# 3. Create tsconfig.json and tsconfig.esm.json
# 4. Create README.md
# 5. Update pnpm-workspace.yaml to add 'sdk/*'
Option B: Create in packages/ first, then move
# 1. Create in packages/
cd /path/to/workspace
pgpm init -t pnpm/module --no-tty --moduleName my-package ...
# 2. Move to target directory
mkdir -p sdk
mv packages/my-package sdk/
# 3. Update pnpm-workspace.yaml
Verification
pnpm install
pnpm build
# Should install and build successfully
Phase 2.5: pgpm init -w / template init hangs (waiting for input)
Problem
When running Next.js template initialization, the command hangs (especially noticeable in non-interactive environments), appearing to wait indefinitely for input parameters (e.g., moduleName).
Cause
pgpm init in non-TTY / agent environments may enter interactive prompts and cause “hung waiting for input” if required parameters are missing.
Solution
- Prefer using
--no-ttyand explicitly providing required parameters (see thepgpm initnon-interactive mode section under Phase 2.1 in this file) - Or ensure you're running in a truly interactive terminal
Verification
The initialization command finishes within a reasonable time and generates the app directory and package.json.
Phase 2.5: Next.js 500 after start (missing @sdk/* / generated output)
Problem
After starting pnpm dev, pages return 500 errors. Common log messages include:
Cannot find module '@sdk/auth'(or similar@sdk/*)- Cannot find the generated GraphQL SDK output directory/files
Cause
This template typically depends on codegen-generated @sdk/* artifacts (located in the app's src/graphql/... or similar directory). If you run pnpm dev without first running pnpm codegen, the missing modules will cause 500 errors.
Solution
Run in the app directory:
pnpm install
pnpm codegen
pnpm dev
Verification
After pnpm dev, pages load normally; and @sdk/* imports no longer throw errors.
Phase 3: Auth hook onSuccess cannot get password (Per-DB login needs mutationFn to return password)
Problem
When calling appSignIn(email, password) in the boilerplate's login hook onSuccess, the password is not available (undefined or inaccessible), causing Per-DB login to fail.
Cause
The onSuccess callback only receives the mutation's return value. If mutationFn only returns { token, email, rememberMe } without password, you cannot access the user's just-entered password in onSuccess (it wasn't passed down via closure or parameters).
Solution
Make mutationFn's return value include password, so onSuccess can destructure it:
- For example:
return { token, email, password, rememberMe } - In
onSuccess:const { token, email, password } = data; await appSignIn(email, password);
Do not rely on “getting password from somewhere else in onSuccess” - if the mutation didn't return it, don't assume you can get it.
Verification
After login, Per-DB's appSignIn executes correctly, and the app token is present in localStorage.
Phase 3: configure-app-sdk headers type error (pass object, not function)
Problem
Writing this in src/lib/configure-app-sdk.ts:
configure({
endpoint: APP_ENDPOINT,
headers: () => {
const token = getAppToken();
return token ? { Authorization: `Bearer ${token}` } : {};
},
});
Build or runtime error: type mismatch, or SDK requests at runtime don't include Authorization.
Cause
The generated SDK's createClient / configure expects headers: Record<string, string> (a plain object), not a function. Passing a function causes type errors or unexpected behavior.
Solution
Compute the headers object first, then pass it in:
function getHeaders(): Record<string, string> {
const token = getAppToken();
return token ? { Authorization: `Bearer ${token}` } : {};
}
configure({
endpoint: APP_ENDPOINT,
headers: getHeaders(),
});
If the token changes after login, you need to call configure({ endpoint, headers: getHeaders() }) again after successful login, or use an adapter that provides headers on each request.
Verification
SDK requests correctly include Authorization: Bearer <token>, with no type errors.
Phase 3: Route structure (don't assume route groups; check template app directory first)
Problem
When creating new features, you placed pages under an assumed route group, for example:
src/app/(authenticated)/my-feature/page.tsx
But you discover that other pages in the template are not under (authenticated), or the template doesn't use that route group at all, causing routing anomalies or inconsistent page styles.
Cause
Different Next.js templates organize routes differently. Some templates use route groups like (authenticated), while others place pages directly under app/ (e.g., app/users/, app/account/). Do not assume based on experience - you must first examine the current template's actual structure.
Solution
- First examine the template's existing structure: Check what directories are under
src/app/and whether there are(xxx)route groups. - Cross-reference
src/app-routes.ts: The route registry reflects the template's conventional structure (e.g., paths forusers,account, etc.). - Keep new features consistent with existing pages: If the template uses a flat structure like
app/users/,app/account/, new features should also go underapp/<feature>/- don't create route groups that don't appear in the template.
Verification
New page paths are consistent with other template pages, accessible normally through app-routes.ts, with no 404 or layout issues.
Phase 3: Don't wrap page in AppShell again (layout already has AuthenticatedShell)
Problem
Wrapping page content in <AppShell>, for example:
export default function BoardsPage() {
return (
<AppShell navigation={...} topBar={...}>
...
</AppShell>
);
}
Runtime error about missing navigation, topBar props, or duplicate/broken layout.
Cause
The template's layout already wraps all child pages in AuthenticatedShell (or an equivalent component), providing a unified shell (navigation, top bar, etc.). Pages themselves do not need another AppShell wrapper. SKILL.md may not explicitly state this, making it easy to assume “one shell per page”.
…(truncated)