{"record":{"id":"64c03a64e720bf6b","repo":"CherryHQ/cherry-studio","slug":"heartbeat-workspace-must-be-user-owned-workspac","errorCode":null,"errorMessage":"Heartbeat workspace must be user-owned: ${workspace.workspaceId}","messagePattern":"Heartbeat workspace must be user-owned: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/agents/runAgentTask.ts","lineNumber":195,"sourceCode":"        throw new Error(`Unsupported heartbeat workspace source: ${String(exhaustive)}`)\n      }\n    }\n    let workspaceRow: Awaited<ReturnType<typeof agentWorkspaceService.getById>>\n    try {\n      workspaceRow = agentWorkspaceService.getById(workspace.workspaceId)\n    } catch (error) {\n      if (isDataApiError(error) && error.code === ErrorCode.NOT_FOUND) {\n        logger.debug('Heartbeat skipped (workspace deleted)', {\n          agentId,\n          scheduleId,\n          workspaceId: workspace.workspaceId\n        })\n        return { sessionId: null, result: 'Skipped (workspace deleted)' }\n      }\n      throw error\n    }\n    if (workspaceRow.type !== AGENT_WORKSPACE_TYPE.USER) {\n      throw new Error(`Heartbeat workspace must be user-owned: ${workspace.workspaceId}`)\n    }\n    const workspacePath = workspaceRow.path\n    const content = await readHeartbeat(workspacePath)\n    if (!content) {\n      logger.debug('Heartbeat skipped (no heartbeat.md)', { agentId, scheduleId })\n      return { sessionId: null, result: 'Skipped (no file)' }\n    }\n    effectivePrompt = [\n      '[Heartbeat]',\n      'This is a periodic heartbeat. The instructions below are from your heartbeat.md file.',\n      'Process each item, take action where possible, and use the notify tool to alert the user of important results.',\n      '',\n      '---',\n      content\n    ].join('\\n')\n  }\n\n  const expectedReuseRevision = normalizeTaskSessionReuseRevision(ctx.input.reuseRevision)","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/agents/runAgentTask.ts#L177-L213","documentation":"Thrown during heartbeat handling in runAgentTask: the heartbeat resolves its target workspace row (via the DataApi), and that workspace's type is not AGENT_WORKSPACE_TYPE.USER. Heartbeats read heartbeat.md from a real on-disk user workspace path, so a system/builtin workspace is not a valid heartbeat target.","triggerScenarios":"A heartbeat-enabled agent task runs (taskName === HEARTBEAT_TASK_NAME and prompt === HEARTBEAT_PROMPT_SENTINEL) whose resolved workspace row has type 'system' (or any non-user type).","commonSituations":"A heartbeat schedule was configured against a system workspace instead of a user workspace; a workspace was downgraded/changed to system after the schedule was created; stale schedule rows referencing the wrong workspace type.","solutions":["Re-point the agent's heartbeat schedule at a user-owned workspace (AGENT_WORKSPACE_TYPE.USER).","When changing a workspace type, validate/migrate any heartbeat schedules bound to it.","If the workspace was deleted/recreated, update the schedule's workspaceId to the new user workspace.","Disable the heartbeat (config.heartbeat_enabled = false) for agents that legitimately use a system workspace."],"exampleFix":"// before: heartbeat scheduled against a system workspace\nschedule: { workspace: { type: 'system' } }\n\n// after: bind heartbeat to a user workspace\nschedule: { workspace: { type: 'user', workspaceId: 'ws-123' } }","handlingStrategy":"validation","validationCode":"import { AGENT_WORKSPACE_TYPE } from '@shared/data/api/schemas/agentWorkspaces'\nconst ws = await resolveWorkspace(workspace)\nif (!ws || ws.type !== AGENT_WORKSPACE_TYPE.USER) {\n  // disable the heartbeat or rebind the schedule to a user workspace\n  await jobScheduleService.update(scheduleId, { active: false })\n  return\n}","typeGuard":"function isUserWorkspace(w: { type: string } | null | undefined): boolean {\n  return w?.type === 'user'\n}","tryCatchPattern":"try {\n  return await runAgentTask(ctx)\n} catch (e) {\n  if (e instanceof Error && /Heartbeat workspace must be user-owned/.test(e.message)) {\n    // rebind or disable the misconfigured heartbeat schedule\n    await jobScheduleService.update(scheduleId, { active: false }).catch(() => {})\n    return { sessionId: null, result: 'Skipped (heartbeat misconfigured)' }\n  } else throw e\n}","preventionTips":["Bind heartbeat schedules only to user-owned workspaces.","Validate workspace type when schedules are created or workspaces change type.","Disable heartbeat for agents on system workspaces."],"tags":["agents","heartbeat","schedules","workspaces","config"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}