fix(host-service): time out AI workspace naming after 5s#4102
fix(host-service): time out AI workspace naming after 5s#4102saddlepaddle merged 1 commit intomainfrom
Conversation
mastra agent.generate had no timeout, so a hang in the small model call would block workspaces.create indefinitely. Wrap the generation in a 5s race; on timeout the existing null-fallback kicks in (friendly random branch name + branch as title).
Greptile SummaryAdds a 5-second timeout guard to both
Confidence Score: 4/5The timeout fix is directionally correct and the fallback path is already in place; the main concern is a timer resource leak on every successful AI call that could accumulate under load. The core change is straightforward and the fallback behavior is pre-existing and well-tested. The clearTimeout omission means a 5-second timer fires after every fast AI response, creating a modest but real resource leak in a high-throughput workspace-creation flow. The duplicated constant is a minor maintenance hazard. Both ai-workspace-names.ts and ai-branch-name.ts share the same timer-leak pattern and should be updated in tandem.
|
| Filename | Overview |
|---|---|
| packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts | Wraps agent.generate in a 5 s Promise.race timeout; the setTimeout handle is never cleared on success, leaving a dangling timer per successful call. Also independently redeclares the shared timeout constant. |
| packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts | Wraps generateTitleFromMessage in the same 5 s Promise.race timeout pattern; same timer-leak issue — clearTimeout is never called when the AI call wins the race. |
Sequence Diagram
sequenceDiagram
participant Caller as workspaces.create
participant AWNP as generateWorkspaceNamesFromPrompt
participant Race as Promise.race
participant AI as agent.generate (Mastra)
participant Timer as setTimeout(5 s)
Caller->>AWNP: prompt
AWNP->>Race: start race
Race->>AI: generate(cleaned, structuredOutput)
Race->>Timer: setTimeout 5000ms
alt AI responds within 5 s (happy path)
AI-->>Race: { object }
Race-->>AWNP: resolved
Note over Timer: timer still runs for ~5s more (not cleared)
AWNP-->>Caller: GeneratedWorkspaceNames
else AI stalls / provider error
Timer-->>Race: reject(timed out after 5000ms)
Race-->>AWNP: catch(error)
AWNP-->>Caller: null → fallback to generateFriendlyBranchName()
end
Comments Outside Diff (1)
-
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts, line 37-58 (link)Same timer-leak pattern as in
ai-workspace-names.ts: thesetTimeouthandle is never cleared whengenerateTitleFromMessagewins the race. The dangling timer fires 5 s later for every successful call, accumulating live handles under concurrent load.Prompt To Fix With AI
This is a comment left during a code review. Path: packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts Line: 37-58 Comment: Same timer-leak pattern as in `ai-workspace-names.ts`: the `setTimeout` handle is never cleared when `generateTitleFromMessage` wins the race. The dangling timer fires 5 s later for every successful call, accumulating live handles under concurrent load. How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts:84-100
Timer leak on the happy path: when `agent.generate` resolves before the 5 s deadline, the `setTimeout` callback is never cancelled. The timer fires 5 seconds later, calls `reject` on an already-settled promise (harmless but wasteful), and the dangling handle keeps the garbage collector from reclaiming the closure. Under load, many concurrent workspace-creation calls will accumulate dozens of live timers simultaneously. Clearing the handle when the race resolves avoids this.
```suggestion
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
try {
const { object } = await Promise.race([
agent.generate(cleaned, {
structuredOutput: {
schema: workspaceNamesSchema,
jsonPromptInjection: true,
},
}),
new Promise<never>((_, reject) => {
timeoutHandle = setTimeout(
() => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
GENERATE_TIMEOUT_MS,
);
}),
]);
clearTimeout(timeoutHandle);
return object;
} catch (error) {
clearTimeout(timeoutHandle);
```
### Issue 2 of 3
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts:37-58
Same timer-leak pattern as in `ai-workspace-names.ts`: the `setTimeout` handle is never cleared when `generateTitleFromMessage` wins the race. The dangling timer fires 5 s later for every successful call, accumulating live handles under concurrent load.
```suggestion
let generated: string | null;
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
try {
generated = await Promise.race([
generateTitleFromMessage({
message: prompt,
agentModel: model,
agentId: "branch-namer",
agentName: "Branch Namer",
instructions: BRANCH_NAME_INSTRUCTIONS,
tracingContext: { surface: "host-service-branch-name" },
}),
new Promise<never>((_, reject) => {
timeoutHandle = setTimeout(
() => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
GENERATE_TIMEOUT_MS,
);
}),
]);
clearTimeout(timeoutHandle);
} catch (error) {
clearTimeout(timeoutHandle);
console.warn("[generateBranchNameFromPrompt] generation failed:", error);
return null;
}
```
### Issue 3 of 3
packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts:12
`GENERATE_TIMEOUT_MS` is now declared independently in both `ai-workspace-names.ts` and `ai-branch-name.ts`. If the desired timeout is ever adjusted, both files must be updated in sync — a silent divergence risk. Consider moving this constant to a shared `constants.ts` (or `ai-utils.ts`) in the same `utils/` directory and importing it from both call-sites.
Reviews (1): Last reviewed commit: "fix(host-service): time out AI workspace..." | Re-trigger Greptile
| try { | ||
| const { object } = await agent.generate(cleaned, { | ||
| structuredOutput: { | ||
| schema: workspaceNamesSchema, | ||
| jsonPromptInjection: true, | ||
| }, | ||
| }); | ||
| const { object } = await Promise.race([ | ||
| agent.generate(cleaned, { | ||
| structuredOutput: { | ||
| schema: workspaceNamesSchema, | ||
| jsonPromptInjection: true, | ||
| }, | ||
| }), | ||
| new Promise<never>((_, reject) => | ||
| setTimeout( | ||
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | ||
| GENERATE_TIMEOUT_MS, | ||
| ), | ||
| ), | ||
| ]); | ||
| return object; | ||
| } catch (error) { |
There was a problem hiding this comment.
Timer leak on the happy path: when
agent.generate resolves before the 5 s deadline, the setTimeout callback is never cancelled. The timer fires 5 seconds later, calls reject on an already-settled promise (harmless but wasteful), and the dangling handle keeps the garbage collector from reclaiming the closure. Under load, many concurrent workspace-creation calls will accumulate dozens of live timers simultaneously. Clearing the handle when the race resolves avoids this.
| try { | |
| const { object } = await agent.generate(cleaned, { | |
| structuredOutput: { | |
| schema: workspaceNamesSchema, | |
| jsonPromptInjection: true, | |
| }, | |
| }); | |
| const { object } = await Promise.race([ | |
| agent.generate(cleaned, { | |
| structuredOutput: { | |
| schema: workspaceNamesSchema, | |
| jsonPromptInjection: true, | |
| }, | |
| }), | |
| new Promise<never>((_, reject) => | |
| setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ), | |
| ), | |
| ]); | |
| return object; | |
| } catch (error) { | |
| let timeoutHandle: ReturnType<typeof setTimeout> | undefined; | |
| try { | |
| const { object } = await Promise.race([ | |
| agent.generate(cleaned, { | |
| structuredOutput: { | |
| schema: workspaceNamesSchema, | |
| jsonPromptInjection: true, | |
| }, | |
| }), | |
| new Promise<never>((_, reject) => { | |
| timeoutHandle = setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ); | |
| }), | |
| ]); | |
| clearTimeout(timeoutHandle); | |
| return object; | |
| } catch (error) { | |
| clearTimeout(timeoutHandle); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
Line: 84-100
Comment:
Timer leak on the happy path: when `agent.generate` resolves before the 5 s deadline, the `setTimeout` callback is never cancelled. The timer fires 5 seconds later, calls `reject` on an already-settled promise (harmless but wasteful), and the dangling handle keeps the garbage collector from reclaiming the closure. Under load, many concurrent workspace-creation calls will accumulate dozens of live timers simultaneously. Clearing the handle when the race resolves avoids this.
```suggestion
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
try {
const { object } = await Promise.race([
agent.generate(cleaned, {
structuredOutput: {
schema: workspaceNamesSchema,
jsonPromptInjection: true,
},
}),
new Promise<never>((_, reject) => {
timeoutHandle = setTimeout(
() => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
GENERATE_TIMEOUT_MS,
);
}),
]);
clearTimeout(timeoutHandle);
return object;
} catch (error) {
clearTimeout(timeoutHandle);
```
How can I resolve this? If you propose a fix, please make it concise.|
|
||
| const WORKSPACE_TITLE_MAX = 150; | ||
| const BRANCH_NAME_MAX = 25; | ||
| const GENERATE_TIMEOUT_MS = 5_000; |
There was a problem hiding this comment.
GENERATE_TIMEOUT_MS is now declared independently in both ai-workspace-names.ts and ai-branch-name.ts. If the desired timeout is ever adjusted, both files must be updated in sync — a silent divergence risk. Consider moving this constant to a shared constants.ts (or ai-utils.ts) in the same utils/ directory and importing it from both call-sites.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
Line: 12
Comment:
`GENERATE_TIMEOUT_MS` is now declared independently in both `ai-workspace-names.ts` and `ai-branch-name.ts`. If the desired timeout is ever adjusted, both files must be updated in sync — a silent divergence risk. Consider moving this constant to a shared `constants.ts` (or `ai-utils.ts`) in the same `utils/` directory and importing it from both call-sites.
How can I resolve this? If you propose a fix, please make it concise.
📝 WalkthroughWalkthroughTwo AI generation utilities now enforce hard 5-second timeouts using ChangesTimeout Enforcement for AI Generation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts (1)
9-9: ⚡ Quick winExtract
GENERATE_TIMEOUT_MSto a shared constant.
GENERATE_TIMEOUT_MS = 5_000is defined identically in bothai-branch-name.tsandai-workspace-names.ts. A future timeout change needs to be applied in two places. Extract it to a singleconstants.tsin the sameutils/directory and import it from both files. As per coding guidelines, constants should be co-located next to the files using them — a shared file within the same directory satisfies this.♻️ Proposed refactor
New file
utils/constants.ts:export const GENERATE_TIMEOUT_MS = 5_000;Then in both files:
+import { GENERATE_TIMEOUT_MS } from "./constants"; -const GENERATE_TIMEOUT_MS = 5_000;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts` at line 9, Create a new constants module in the same utils directory (e.g., constants.ts) that exports the shared constant GENERATE_TIMEOUT_MS = 5_000, then replace the local GENERATE_TIMEOUT_MS definitions in both ai-branch-name.ts and ai-workspace-names.ts with an import from that new constants module; update both files to import { GENERATE_TIMEOUT_MS } from './constants' and remove the duplicate local constant declarations so the timeout is maintained in a single place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts`:
- Around line 39-54: The Promise.race call with generateTitleFromMessage and
setTimeout leaks the timer because the timeout still fires if
generateTitleFromMessage resolves first; change the code in ai-branch-name.ts so
you capture the setTimeout handle (e.g. const timeoutId = setTimeout(...)) and
ensure you clearTimeout(timeoutId) once the race settles (use a finally block or
clear after await) around the Promise.race that assigns generated; keep
references to generateTitleFromMessage, BRANCH_NAME_INSTRUCTIONS, and
GENERATE_TIMEOUT_MS so the timeout duration and agent call remain unchanged
while preventing the timer leak.
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`:
- Around line 85-98: The Promise.race that calls agent.generate(...) alongside a
setTimeout creates a dangling timer when agent.generate resolves first; modify
the implementation around agent.generate, workspaceNamesSchema and
GENERATE_TIMEOUT_MS to store the timer handle returned by setTimeout and ensure
you clear it (clearTimeout) once agent.generate settles (either in a finally
block or immediately after the race resolves) so the timeout callback is
cancelled and no dangling timers accumulate.
---
Nitpick comments:
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts`:
- Line 9: Create a new constants module in the same utils directory (e.g.,
constants.ts) that exports the shared constant GENERATE_TIMEOUT_MS = 5_000, then
replace the local GENERATE_TIMEOUT_MS definitions in both ai-branch-name.ts and
ai-workspace-names.ts with an import from that new constants module; update both
files to import { GENERATE_TIMEOUT_MS } from './constants' and remove the
duplicate local constant declarations so the timeout is maintained in a single
place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 31e9c13d-01d6-467b-aacf-fe6f13e3ce64
📒 Files selected for processing (2)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.tspackages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
| generated = await Promise.race([ | ||
| generateTitleFromMessage({ | ||
| message: prompt, | ||
| agentModel: model, | ||
| agentId: "branch-namer", | ||
| agentName: "Branch Namer", | ||
| instructions: BRANCH_NAME_INSTRUCTIONS, | ||
| tracingContext: { surface: "host-service-branch-name" }, | ||
| }), | ||
| new Promise<never>((_, reject) => | ||
| setTimeout( | ||
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | ||
| GENERATE_TIMEOUT_MS, | ||
| ), | ||
| ), | ||
| ]); |
There was a problem hiding this comment.
Same timer-leak as in ai-workspace-names.ts — clear the timeout handle.
The setTimeout fires even when generateTitleFromMessage completes within 5 s. The same finally(() => clearTimeout(timeoutId)) fix applies here.
🛠️ Proposed fix
try {
- generated = await Promise.race([
- generateTitleFromMessage({
- message: prompt,
- agentModel: model,
- agentId: "branch-namer",
- agentName: "Branch Namer",
- instructions: BRANCH_NAME_INSTRUCTIONS,
- tracingContext: { surface: "host-service-branch-name" },
- }),
- new Promise<never>((_, reject) =>
- setTimeout(
- () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
- GENERATE_TIMEOUT_MS,
- ),
- ),
- ]);
+ let timeoutId: ReturnType<typeof setTimeout>;
+ generated = await Promise.race([
+ generateTitleFromMessage({
+ message: prompt,
+ agentModel: model,
+ agentId: "branch-namer",
+ agentName: "Branch Namer",
+ instructions: BRANCH_NAME_INSTRUCTIONS,
+ tracingContext: { surface: "host-service-branch-name" },
+ }),
+ new Promise<never>((_, reject) => {
+ timeoutId = setTimeout(
+ () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
+ GENERATE_TIMEOUT_MS,
+ );
+ }),
+ ]).finally(() => clearTimeout(timeoutId));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| generated = await Promise.race([ | |
| generateTitleFromMessage({ | |
| message: prompt, | |
| agentModel: model, | |
| agentId: "branch-namer", | |
| agentName: "Branch Namer", | |
| instructions: BRANCH_NAME_INSTRUCTIONS, | |
| tracingContext: { surface: "host-service-branch-name" }, | |
| }), | |
| new Promise<never>((_, reject) => | |
| setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ), | |
| ), | |
| ]); | |
| let timeoutId: ReturnType<typeof setTimeout>; | |
| generated = await Promise.race([ | |
| generateTitleFromMessage({ | |
| message: prompt, | |
| agentModel: model, | |
| agentId: "branch-namer", | |
| agentName: "Branch Namer", | |
| instructions: BRANCH_NAME_INSTRUCTIONS, | |
| tracingContext: { surface: "host-service-branch-name" }, | |
| }), | |
| new Promise<never>((_, reject) => { | |
| timeoutId = setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ); | |
| }), | |
| ]).finally(() => clearTimeout(timeoutId)); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts`
around lines 39 - 54, The Promise.race call with generateTitleFromMessage and
setTimeout leaks the timer because the timeout still fires if
generateTitleFromMessage resolves first; change the code in ai-branch-name.ts so
you capture the setTimeout handle (e.g. const timeoutId = setTimeout(...)) and
ensure you clearTimeout(timeoutId) once the race settles (use a finally block or
clear after await) around the Promise.race that assigns generated; keep
references to generateTitleFromMessage, BRANCH_NAME_INSTRUCTIONS, and
GENERATE_TIMEOUT_MS so the timeout duration and agent call remain unchanged
while preventing the timer leak.
| const { object } = await Promise.race([ | ||
| agent.generate(cleaned, { | ||
| structuredOutput: { | ||
| schema: workspaceNamesSchema, | ||
| jsonPromptInjection: true, | ||
| }, | ||
| }), | ||
| new Promise<never>((_, reject) => | ||
| setTimeout( | ||
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | ||
| GENERATE_TIMEOUT_MS, | ||
| ), | ||
| ), | ||
| ]); |
There was a problem hiding this comment.
Clear the timeout handle when agent.generate wins the race.
When the LLM responds before 5 s the setTimeout still fires, allocates an Error, and calls the already-no-op reject. In a busy host-service process this accumulates one dangling 5-second timer per successful workspace creation. Storing the handle and clearing it in a finally (or in each branch) removes the waste entirely.
🛠️ Proposed fix
try {
- const { object } = await Promise.race([
- agent.generate(cleaned, {
- structuredOutput: {
- schema: workspaceNamesSchema,
- jsonPromptInjection: true,
- },
- }),
- new Promise<never>((_, reject) =>
- setTimeout(
- () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
- GENERATE_TIMEOUT_MS,
- ),
- ),
- ]);
+ let timeoutId: ReturnType<typeof setTimeout>;
+ const { object } = await Promise.race([
+ agent.generate(cleaned, {
+ structuredOutput: {
+ schema: workspaceNamesSchema,
+ jsonPromptInjection: true,
+ },
+ }),
+ new Promise<never>((_, reject) => {
+ timeoutId = setTimeout(
+ () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)),
+ GENERATE_TIMEOUT_MS,
+ );
+ }),
+ ]).finally(() => clearTimeout(timeoutId));
return object;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const { object } = await Promise.race([ | |
| agent.generate(cleaned, { | |
| structuredOutput: { | |
| schema: workspaceNamesSchema, | |
| jsonPromptInjection: true, | |
| }, | |
| }), | |
| new Promise<never>((_, reject) => | |
| setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ), | |
| ), | |
| ]); | |
| let timeoutId: ReturnType<typeof setTimeout>; | |
| const { object } = await Promise.race([ | |
| agent.generate(cleaned, { | |
| structuredOutput: { | |
| schema: workspaceNamesSchema, | |
| jsonPromptInjection: true, | |
| }, | |
| }), | |
| new Promise<never>((_, reject) => { | |
| timeoutId = setTimeout( | |
| () => reject(new Error(`timed out after ${GENERATE_TIMEOUT_MS}ms`)), | |
| GENERATE_TIMEOUT_MS, | |
| ); | |
| }), | |
| ]).finally(() => clearTimeout(timeoutId)); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts`
around lines 85 - 98, The Promise.race that calls agent.generate(...) alongside
a setTimeout creates a dangling timer when agent.generate resolves first; modify
the implementation around agent.generate, workspaceNamesSchema and
GENERATE_TIMEOUT_MS to store the timer handle returned by setTimeout and ensure
you clear it (clearTimeout) once agent.generate settles (either in a finally
block or immediately after the race resolves) so the timeout callback is
cancelled and no dangling timers accumulate.
Changes since v0.2.8: - `superset agents list` (and demoted "presets" to a UI-only concept). CLI now reads canonical agents from host-service. (#4097) - Host-service: refresh stale OAuth access tokens on remote workspace ops instead of failing the request. (#4106) - Host-service: workspace.create now adopts an existing worktree at any path, not just the canonical one. (#4096) - Host-service: AI workspace naming times out after 5s and falls back to a deterministic name. (#4102) Push cli-v0.2.9 after this lands to fire the release pipeline.
mastra agent.generate had no timeout, so a hang in the small model call would block workspaces.create indefinitely. Wrap the generation in a 5s race; on timeout the existing null-fallback kicks in (friendly random branch name + branch as title).
Changes since v0.2.8: - `superset agents list` (and demoted "presets" to a UI-only concept). CLI now reads canonical agents from host-service. (#4097) - Host-service: refresh stale OAuth access tokens on remote workspace ops instead of failing the request. (#4106) - Host-service: workspace.create now adopts an existing worktree at any path, not just the canonical one. (#4096) - Host-service: AI workspace naming times out after 5s and falls back to a deterministic name. (#4102) Push cli-v0.2.9 after this lands to fire the release pipeline.
Summary
agent.generatehad no timeout — a hang in the small model call (e.g. provider stall) would blockworkspaces.createindefinitely with no fallback.generateWorkspaceNamesFromPromptandgenerateBranchNameFromPromptwith a 5sPromise.race. On timeout the existingcatchreturnsnull, which the create flow already handles by falling back togenerateFriendlyBranchName()(and the branch as title).Test plan
workspaces.createcompletes within ~5s using a friendly random branch name.Summary by cubic
Prevents
workspaces.createfrom hanging by timing out workspace and branch name generation after 5s inhost-service.generateWorkspaceNamesFromPromptandgenerateBranchNameFromPromptin a 5sPromise.race(GENERATE_TIMEOUT_MS).nullto trigger the existing fallback (generateFriendlyBranchName()and use the branch as the title).Written for commit 337fd2d. Summary will update on new commits.
Summary by CodeRabbit