{"record":{"id":"6e2690af51b0a6f2","repo":"block/buzz","slug":"this-agent-is-present-on-the-relay-starting-anoth","errorCode":null,"errorMessage":"This agent is present on the relay. Starting another instance is unavailable.","messagePattern":"This agent is present on the relay\\. Starting another instance is unavailable\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"desktop/src/features/agents/ui/useManagedAgentActions.ts","lineNumber":115,"sourceCode":"  const managedPubkeys = React.useMemo(\n    () => new Set(managedAgents.map((agent) => agent.pubkey)),\n    [managedAgents],\n  );\n\n  const managedPubkeyList = React.useMemo(\n    () => managedAgents.map((agent) => agent.pubkey),\n    [managedAgents],\n  );\n\n  const { query: managedPresenceQuery, getAvailability } =\n    useAgentAvailabilityLookup(managedPubkeyList);\n\n  function assertStartNotBlockedByPresence(agent: ManagedAgent) {\n    const reason = agentPresenceStartBlockReason(\n      isManagedAgentActive(agent),\n      getAvailability(agent.pubkey),\n    );\n    if (reason) throw new Error(reason);\n  }\n\n  const channelsByPubkey = React.useMemo(() => {\n    const map: Record<string, { id: string; name: string }[]> = {};\n    // Seed from relay agent profiles (kind:10100 events).\n    for (const ra of relayAgentsQuery.data ?? []) {\n      if (ra.channels.length > 0) {\n        // Skip entries missing a channel id rather than falling back to the\n        // name as id — a misaligned channels/channelIds pairing would otherwise\n        // produce a pill that silently navigates to a channel name as if it\n        // were an id.\n        map[normalizePubkey(ra.pubkey)] = ra.channels.flatMap((name, i) => {\n          const id = ra.channelIds[i];\n          return id ? [{ id, name }] : [];\n        });\n      }\n    }\n    // Fill in from channel member lists (kind:39002) for any managed agents","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/block/buzz/blob/dad5a33865fc81a2e55b3b60746632f615ec1e3a/desktop/src/features/agents/ui/useManagedAgentActions.ts#L97-L133","documentation":"assertStartNotBlockedByPresence throws when a managed agent's relay presence (online/away) shows the agent is already running somewhere, so the desktop refuses to start a second instance. Availability comes from the presence query snapshot via getAvailability(agent.pubkey), and the block only fires when the local lifecycle is not itself active. The wording is deliberate: positive presence blocks another start but never grants lifecycle control.","triggerScenarios":"Calling handleStart or handleRestart on a managed agent whose pubkey resolves to presence 'online' or 'away' in the current connected presence snapshot, while the local agent record is inactive. Also occurs when another device or session is running the same agent identity.","commonSituations":"User starts the agent from a second desktop device; a stale-but-live presence snapshot still shows the agent online after a crash elsewhere; an old agent process on another machine is still connected to the relay; user clicks Start on a relay-hosted agent that is already deployed.","solutions":["Stop the existing agent instance first (from the device that owns it) and wait for presence to flip to offline, then retry Start.","Verify the relay presence snapshot is current — confirm the app is connected and the presence query has refreshed for this pubkey.","If the other instance is dead but presence lingers, stop/kill the orphaned process and restart the relay session so presence clears.","If the local record is wrong (agent not actually running anywhere), refresh the agent inventory and retry after presence updates."],"exampleFix":"// before\ntoast.success('Started agent');\nawait startManagedAgent(agent);\n\n// after\nconst availability = getAvailability(agent.pubkey);\nconst reason = agentPresenceStartBlockReason(isManagedAgentActive(agent), availability);\nif (reason) {\n  toast.error(reason); // surface the block instead of attempting the start\n  return;\n}\nawait startManagedAgent(agent);","handlingStrategy":"validation","validationCode":"import { agentPresenceStartBlockReason } from \"@/features/agents/lib/useAgentAvailability\";\nconst block = agentPresenceStartBlockReason(isManagedAgentActive(agent), getAvailability(agent.pubkey));\nif (block) { /* disable Start / show reason */ }","typeGuard":"const canStart = (a: ManagedAgent): boolean =>\n  agentPresenceStartBlockReason(isManagedAgentActive(a), getAvailability(a.pubkey)) === undefined;","tryCatchPattern":"try {\n  await handleStart(agent);\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"present on the relay\")) {\n    toast.info(\"This agent is already running elsewhere. Stop it first.\");\n    return;\n  }\n  throw e;\n}","preventionTips":["Read relay presence (getAvailability) before rendering Start buttons and disable them when online/away.","Invalidate the presence query after every stop so subsequent starts see fresh state.","Treat unknown/degraded availability as non-authoritative — never assume a start is safe.","Surface the block reason in the UI instead of letting the throw reach the console."],"tags":["presence","relay","agents","lifecycle"],"backgroundTag":"agent-already-present-on-relay","analyzedSha":"dad5a33865fc81a2e55b3b60746632f615ec1e3a","analyzedAt":"2026-09-05T18:13:50.666Z","contentChangedAt":"2026-09-05T18:13:50.666Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}