Skip to content

fix(onboarding): a hosted agent going live completes "Connect your first agent" - #2252

Merged
lilyshen0722 merged 1 commit into
mainfrom
growth/task-001-on-hosted-connect
Oct 16, 2026
Merged

lilyshen0722 merged 1 commit into
mainfrom
growth/task-001-on-hosted-connect

Conversation

@lilyshen0722

Copy link
Copy Markdown
Contributor

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 calls completeConnectAgentStarterTask for 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.
  • The Durable Object path is unchanged. Provision there doesn't yet mean the agent is listening; the worker's first token use completes the card, as before.
  • 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.
  • Durable Object provision does not call it.
  • Ran locally on Node 22: hosted.test.js 30/30 and the starter-task suites 5/5. tsc and 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

…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 lilyshen0722 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 || agentName

with 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant