nanocoai/nanoclaw · error

agent group not found: ${agentGroupId}

Error message

agent group not found: ${agentGroupId}

What it means

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.

Source

Thrown at src/modules/scheduling/run-log.ts:26

 *     (container/agent-runner poll-loop auto-append; delivery.ts routes it here)
 */
import fs from 'fs';

import { GROUPS_DIR } from '../../config.js';
import { resolveGroupTimezone } from '../../container-config.js';
import { getAgentGroup } from '../../db/agent-groups.js';
import { formatLocalStamp } from '../../timezone.js';

export async function appendRunLog(
  agentGroupId: string,
  series: string,
  msg: string,
): Promise<{ series: string; timestamp: string; path: string }> {
  // Charset guard is the security boundary: blocks path traversal and keeps
  // the id safe as a filename. Callers resolve group scope before this.
  if (!/^[a-z0-9-]+$/.test(series)) throw new Error(`invalid task id: ${series}`);
  const ag = await getAgentGroup(agentGroupId);
  if (!ag) throw new Error(`agent group not found: ${agentGroupId}`);

  const timestamp = formatLocalStamp(new Date(), await resolveGroupTimezone(agentGroupId));
  const dir = `${GROUPS_DIR}/${ag.folder}/tasks`;
  const file = `${dir}/${series}.md`;
  fs.mkdirSync(dir, { recursive: true });
  fs.appendFileSync(file, `${timestamp} — ${msg}\n`);
  return { series, timestamp, path: file };
}

export async function deleteRunLog(agentGroupId: string, series: string): Promise<void> {
  if (!/^[a-z0-9-]+$/.test(series)) throw new Error(`invalid task id: ${series}`);
  const ag = await getAgentGroup(agentGroupId);
  if (!ag) throw new Error(`agent group not found: ${agentGroupId}`);
  fs.rmSync(`${GROUPS_DIR}/${ag.folder}/tasks/${series}.md`, { force: true });
}

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. List existing groups (ncl groups list) and use the exact id.
  2. If the group was deleted but tasks remain, clean up or re-create the group before logging.
  3. Parameterize group ids from config rather than hardcoding them per environment.

Example fix

// before
await appendRunLog('my-group', series, 'ran');

// after
const groups = await listAgentGroups();
const g = groups.find(x => x.name === 'my-group');
await appendRunLog(g.id, series, 'ran');
Defensive patterns

Strategy: try-catch

Validate before calling

import { getAgentGroup } from './db/agent-groups';
const ag = await getAgentGroup(agentGroupId);
if (!ag) throw new Error(`unknown agent group: ${agentGroupId}`);

Try / catch

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; }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/558163d2efcd2a8f. Report an issue: GitHub.