{"record":{"id":"558163d2efcd2a8f","repo":"nanocoai/nanoclaw","slug":"agent-group-not-found-agentgroupid-558163","errorCode":null,"errorMessage":"agent group not found: ${agentGroupId}","messagePattern":"agent group not found: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/modules/scheduling/run-log.ts","lineNumber":26,"sourceCode":" *     (container/agent-runner poll-loop auto-append; delivery.ts routes it here)\n */\nimport fs from 'fs';\n\nimport { GROUPS_DIR } from '../../config.js';\nimport { resolveGroupTimezone } from '../../container-config.js';\nimport { getAgentGroup } from '../../db/agent-groups.js';\nimport { formatLocalStamp } from '../../timezone.js';\n\nexport async function appendRunLog(\n  agentGroupId: string,\n  series: string,\n  msg: string,\n): Promise<{ series: string; timestamp: string; path: string }> {\n  // Charset guard is the security boundary: blocks path traversal and keeps\n  // the id safe as a filename. Callers resolve group scope before this.\n  if (!/^[a-z0-9-]+$/.test(series)) throw new Error(`invalid task id: ${series}`);\n  const ag = await getAgentGroup(agentGroupId);\n  if (!ag) throw new Error(`agent group not found: ${agentGroupId}`);\n\n  const timestamp = formatLocalStamp(new Date(), await resolveGroupTimezone(agentGroupId));\n  const dir = `${GROUPS_DIR}/${ag.folder}/tasks`;\n  const file = `${dir}/${series}.md`;\n  fs.mkdirSync(dir, { recursive: true });\n  fs.appendFileSync(file, `${timestamp} — ${msg}\\n`);\n  return { series, timestamp, path: file };\n}\n\nexport async function deleteRunLog(agentGroupId: string, series: string): Promise<void> {\n  if (!/^[a-z0-9-]+$/.test(series)) throw new Error(`invalid task id: ${series}`);\n  const ag = await getAgentGroup(agentGroupId);\n  if (!ag) throw new Error(`agent group not found: ${agentGroupId}`);\n  fs.rmSync(`${GROUPS_DIR}/${ag.folder}/tasks/${series}.md`, { force: true });\n}\n","sourceCodeStart":8,"sourceCodeEnd":42,"githubUrl":"https://github.com/nanocoai/nanoclaw/blob/294ef2aee85218b23ad30eda9dfe10e590b54a8c/src/modules/scheduling/run-log.ts#L8-L42","documentation":"appendRunLog looks up the agent group in the central DB to resolve its folder under groups/. If getAgentGroup returns null the group id does not exist (never created, deleted, or typo'd), and the run log cannot be written because the target directory is unknown.","triggerScenarios":"Calling appendRunLog/appendTaskLog/appendHostTaskNote with an agentGroupId that is not present in the agent_groups table — a stale id after the group was deleted, a copy-pasted id from another install, or a truncated id.","commonSituations":"Tasks outliving their agent group after deletion; scripts hardcoding group ids across environments; referencing a group by folder name or display name instead of its id.","solutions":["List existing groups (ncl groups list) and use the exact id.","If the group was deleted but tasks remain, clean up or re-create the group before logging.","Parameterize group ids from config rather than hardcoding them per environment."],"exampleFix":"// before\nawait appendRunLog('my-group', series, 'ran');\n\n// after\nconst groups = await listAgentGroups();\nconst g = groups.find(x => x.name === 'my-group');\nawait appendRunLog(g.id, series, 'ran');","handlingStrategy":"try-catch","validationCode":"import { getAgentGroup } from './db/agent-groups';\nconst ag = await getAgentGroup(agentGroupId);\nif (!ag) throw new Error(`unknown agent group: ${agentGroupId}`);","typeGuard":null,"tryCatchPattern":"try { await appendRunLog(groupId, series, msg); } catch (err) { if ((err as Error).message.startsWith('agent group not found')) { /* skip or re-resolve group */ } else throw err; }","preventionTips":["Resolve the group id once at startup and reuse it.","Don't hardcode group ids across environments; load from config."],"tags":["database","lookup","agent-groups","tasks"],"backgroundTag":"entity-not-found","analyzedSha":"294ef2aee85218b23ad30eda9dfe10e590b54a8c","analyzedAt":"2026-08-28T13:59:10.357Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}