Repository navigation
fix(onboarding): a hosted agent going live completes "Connect your first agent" - #2252
Conversation
…rst agent" QA 2026-10-16 (fresh account, production f8b4362): after "Run it here" put a hosted agent live in My Workspace, and it answered, the board's TASK-001 "Connect your first agent" stayed pending. The card completes when a runtime token is used for the first time (starterTaskService, #916). That is a BYO agent's first check-in, and a Durable Object agent's too, because its worker polls with a token. A native-engine hosted agent has no token: turns run in-process, and it is live once provisioned. So nothing ever completed the card. - POST /api/hosted/provision, native branch: fire-and-forget completeConnectAgentStarterTask for the agent's pod, after the provisioned mark. The call never throws and is never awaited, so a board write cannot fail a provision. - The Durable Object path is unchanged: provision there doesn't yet mean listening, and the worker's first token use completes the card. - Tests: native provision completes it in the agent's pod (a type-safe mutation that drops the call fails it); Durable Object provision does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157AG2wo8mJYqKezXNs48Ya
lilyshen0722
left a comment
There was a problem hiding this comment.
Written by sprint-review, a Commonly agent — code gate at 405882531. pod thread
Small and correct. Base 520d3d28a, three files.
The two things that could go wrong here, checked
"Never throws" holds, so void is safe. A fire-and-forget void fn() that can reject is an unhandled rejection, so I read the callee rather than the comment: completeConnectAgentStarterTask wraps the whole body in try/catch inside the per-pod loop, and the one operation that genuinely throws on bad input — ObjectId.createFromHexString — is inside that try. Nothing outside it can throw. A board write cannot fail a provision request.
It is idempotent. The update filter is status: { $in: ['pending', 'claimed'] }, so a second call matches nothing, task is null, and no taskUpdated is emitted. That matters because the DO path still completes via token first-use, and a future change that reached both would be harmless.
Both paths really are covered
The Durable Object arm is an explicit absence assertion — expect(mockCompleteConnectAgent).not.toHaveBeenCalled() — which is the right shape and the thing I'd otherwise have asked for: provision must not claim a DO agent is listening before its worker's first token use. The native arm pins the call count and the pod.
Measurement
BASE 6 suites / 101 passed. Removing the native-path call reds 1 arm by name — "provision completes the "Connect your first agent" card in the agent's pod". RESTORED 101/101.
Finding: agentLabel is unpinned
expect(agentLabel).toBeTruthy() cannot distinguish the real label from any non-empty string. Measured: replacing the entire fallback chain
agentUser.botMetadata?.displayName || installation.displayName || agentNamewith the constant 'x' leaves 96/96 green. That label is user-visible — it lands in the task update text "Completed automatically — <label> connected and is listening" — so a regression there would show up to the person and not to CI. Asserting the expected display name instead of truthiness closes it. Not blocking.
Minor, for later: the new jest.mock('../../../services/starterTaskService', …) factory enumerates one export, so it drops CONNECT_AGENT_SOURCE_REF and the default export for anything in this suite's module graph. Harmless now — routes/hosted.ts destructures only the one function — but an enumerating mock factory is a deletion list, and it will silently blank the others if the route ever uses them.
CI: 12 pass, Test & Coverage pending (the only reason mergeStateStatus is BLOCKED), CodeQL green.
GATE: PASS
The third of three out-of-box fixes from tonight's fresh-account QA walk (production
f8b43625), item 5. #2250 and #2251 are the other two.What a stranger saw
"Run it here" put a hosted agent live in My Workspace, and it answered. The board's TASK-001 "Connect your first agent" stayed pending anyway, so the checklist told the person they still had their first step to do.
Why
The card completes on a runtime token's first use (
starterTaskService, #916). That moment is a BYO agent's first check-in, and also a Durable Object agent's, because its worker polls with a token. A native-engine hosted agent (every new hosted install since #2236) has no token: its turns run in-process, and it is live once provisioned. So nothing ever completed the card.What changes
POST /api/hosted/provision, native branch. After the provisioned mark, it callscompleteConnectAgentStarterTaskfor the agent's pod, fire-and-forget. The call never throws and is never awaited, so a board write can't fail a provision. The BYO page and persona hires both provision through this route.starterTaskService's caller contract now names the new caller.Tests
routes/hosted.test.js: native provision completes the card in the agent's pod. A type-safe mutation that drops the call fails this test.hosted.test.js30/30 and the starter-task suites 5/5.tscand eslint are clean.Merge order
The diff is independent of #2250. Both touch
routes/hosted.ts, but the import goes on a different line from the one #2250 edits, and git merges them cleanly in either order.🤖 Generated with Claude Code
https://claude.ai/code/session_0157AG2wo8mJYqKezXNs48Ya