{"record":{"id":"0fa31d7151051a93","repo":"santifer/career-ops","slug":"set-notion-parent-page-id-in-env-the-career-ops","errorCode":null,"errorMessage":"Set NOTION_PARENT_PAGE_ID in .env (the \"Career Ops\" parent page id).","messagePattern":"Set NOTION_PARENT_PAGE_ID in \\.env \\(the \"Career Ops\" parent page id\\)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"plugins/notion/_notion.mjs","lineNumber":90,"sourceCode":"    await sleep(360); // ~3 req/s\n    // ctx.fetch throws on non-2xx (its message carries the body); the !r.ok\n    // branch below is the fallback when a plain global fetch is injected.\n    const r = await fetchFn(`https://api.notion.com/v1/${path}`, { method, headers: HEADERS, body: body ? JSON.stringify(body) : undefined });\n    const j = await r.json();\n    if (!r.ok) throw new Error(`Notion ${method} ${path} -> ${j.code}: ${j.message}`);\n    return j;\n  }\n\n  /** Create a page in a data source. `markdown` (optional) becomes the page body. */\n  async function createPage(dataSourceId, properties, markdown) {\n    const body = { parent: { type: 'data_source_id', data_source_id: dataSourceId }, properties };\n    if (markdown) body.markdown = markdown;\n    return api('pages', 'POST', body);\n  }\n\n  /** Map of DB name → primary data source id for every DB under the parent page. */\n  async function resolveDBs() {\n    if (!parent) throw new Error('Set NOTION_PARENT_PAGE_ID in .env (the \"Career Ops\" parent page id).');\n    const out = {};\n    let cursor;\n    do {\n      const j = await api(`blocks/${parent}/children?page_size=100${cursor ? `&start_cursor=${cursor}` : ''}`, 'GET');\n      for (const b of j.results) {\n        if (b.type !== 'child_database') continue;\n        const db = await api(`databases/${b.id}`, 'GET');\n        out[b.child_database.title] = db.data_sources?.[0]?.id;\n      }\n      cursor = j.has_more ? j.next_cursor : null;\n    } while (cursor);\n    return out;\n  }\n\n  async function queryDB(dataSourceId) {\n    let cursor, all = [];\n    do {\n      const j = await api(`data_sources/${dataSourceId}/query`, 'POST', { page_size: 100, start_cursor: cursor });","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/plugins/notion/_notion.mjs#L72-L108","documentation":"resolveDBs() enumerates the child databases under a configured Notion parent page to map each database name to its primary data source id. It requires the parent page id (taken from NOTION_PARENT_PAGE_ID and passed into the client as cfg.parent) to be set; without it there is nothing to enumerate, so it throws immediately with instructions to set the variable in .env.","triggerScenarios":"Calling resolveDBs() (directly or via any plugin operation that needs to locate the Career Ops databases) when NOTION_PARENT_PAGE_ID is missing from .env, the cfg object passed to createNotionClient omitted parent, or the variable is present but empty so it falsy-coerces to the same branch.","commonSituations":"Fresh setup where the user copied the integration token into .env but not the parent page id; the parent page was moved/duplicated and the old id was removed from .env; the id was pasted with the Notion URL's trailing hash formatting stripped incorrectly (though an empty value is the case that throws here); CI runs where only the token secret was configured.","solutions":["Add NOTION_PARENT_PAGE_ID to .env with the 32-hex-char id of the 'Career Ops' parent page (the id segment of the page URL).","Ensure the parent page is shared with your Notion integration (page → … menu → Connections → add the integration), otherwise the id resolves but enumeration returns nothing.","Verify createNotionClient is called with { token, parent } and that the parent value is non-empty after trim.","In CI, add the NOTION_PARENT_PAGE_ID secret alongside the token so both are injected."],"exampleFix":"// before (.env)\nNOTION_ACCESS_TOKEN=secret_xxx\n# NOTION_PARENT_PAGE_ID not set\n\n// after (.env)\nNOTION_ACCESS_TOKEN=secret_xxx\nNOTION_PARENT_PAGE_ID=1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d","handlingStrategy":"validation","validationCode":"// before constructing the client\nconst parent = (process.env.NOTION_PARENT_PAGE_ID || '').trim();\nif (!parent) {\n  throw new Error('NOTION_PARENT_PAGE_ID missing in .env — paste the id of the Career Ops parent page');\n}","typeGuard":"function hasNotionParent(env = process.env): env is typeof env & { NOTION_PARENT_PAGE_ID: string } {\n  return typeof env.NOTION_PARENT_PAGE_ID === 'string' && /^[0-9a-f]{32}$/i.test(env.NOTION_PARENT_PAGE_ID.trim());\n}","tryCatchPattern":"try {\n  const dbs = await client.resolveDBs();\n} catch (e) {\n  if (/NOTION_PARENT_PAGE_ID/.test(e.message)) {\n    console.error('Add NOTION_PARENT_PAGE_ID=<32-hex page id> to .env, then share that page with your integration.');\n    process.exit(1);\n  }\n  throw e;\n}","preventionTips":["Copy the parent page id from the Notion page URL into .env during initial setup, next to the token.","Check both Notion vars together in one startup validation step (token + parent).","Remember that setting the id is not enough — the page must also be connected to the integration.","Re-verify the id after moving or duplicating the parent page in Notion."],"tags":["configuration","environment","notion","setup"],"backgroundTag":"missing-env-var","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}