Skip to content

fix(host-service): time out AI workspace naming after 5s#4102

Merged
saddlepaddle merged 1 commit intomainfrom
workspace-create-fallback
May 5, 2026
Merged

fix(host-service): time out AI workspace naming after 5s#4102
saddlepaddle merged 1 commit intomainfrom
workspace-create-fallback

Conversation

@saddlepaddle
Copy link
Copy Markdown
Collaborator

@saddlepaddle saddlepaddle commented May 5, 2026

Summary

  • mastra agent.generate had no timeout — a hang in the small model call (e.g. provider stall) would block workspaces.create indefinitely with no fallback.
  • Wrap generateWorkspaceNamesFromPrompt and generateBranchNameFromPrompt with a 5s Promise.race. On timeout the existing catch returns null, which the create flow already handles by falling back to generateFriendlyBranchName() (and the branch as title).
  • PR-create path is unaffected — it never calls AI naming (uses GitHub PR title + derived branch).

Test plan

  • Force a hang in the small model (e.g. invalid creds / network blackhole) and confirm workspaces.create completes within ~5s using a friendly random branch name.
  • Normal happy path still produces an AI title + branch.
  • PR create still uses the PR title.

Summary by cubic

Prevents workspaces.create from hanging by timing out workspace and branch name generation after 5s in host-service.

  • Bug Fixes
    • Wrap generateWorkspaceNamesFromPrompt and generateBranchNameFromPrompt in a 5s Promise.race (GENERATE_TIMEOUT_MS).
    • On timeout or error, return null to trigger the existing fallback (generateFriendlyBranchName() and use the branch as the title).
    • PR creation path is unchanged; it uses the GitHub PR title.

Written for commit 337fd2d. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Improved reliability of AI-powered name generation for branches and workspaces by adding timeout protection to prevent operations from hanging indefinitely.

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-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 5, 2026

Greptile Summary

Adds a 5-second timeout guard to both generateWorkspaceNamesFromPrompt (structured-output Mastra agent) and generateBranchNameFromPrompt to prevent indefinite hangs when the small-model provider stalls. On timeout the existing catch blocks return null, allowing the workspace-creation flow to fall back to friendly random names.

  • Both functions now use Promise.race between the AI call and a rejecting setTimeout promise; when the timeout fires the catch returns null and the caller uses the pre-existing fallback path.
  • The setTimeout handle is not stored or cleared when the AI call completes first, so a live timer fires 5 s after every successful generation — this accumulates under concurrent load.
  • GENERATE_TIMEOUT_MS = 5_000 is independently declared in both files rather than shared from a common constants module.

Confidence Score: 4/5

The 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.

Important Files Changed

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
Loading

Comments Outside Diff (1)

  1. packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts, line 37-58 (link)

    P2 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.

    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

Comment on lines 84 to 100
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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

Suggested change
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;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

Two AI generation utilities now enforce hard 5-second timeouts using Promise.race. Branch-name and workspace-name generation calls are wrapped with timeout-aware logic that rejects if generation exceeds the limit, allowing existing error handlers to gracefully return null.

Changes

Timeout Enforcement for AI Generation

Layer / File(s) Summary
Timeout Constants
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts, packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
GENERATE_TIMEOUT_MS = 5_000 constant added to both files to parameterize generation timeouts.
Timeout Wrappers
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts, packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts
AI generation calls wrapped in Promise.race against timeout promises that reject after 5 seconds, funneling timeout errors through existing error handlers.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A timeout for every dream,
Five seconds quick, not too extreme,
When AI dreams take longer still,
We gracefully return null with goodwill! ⏳

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a 5-second timeout to AI workspace naming to prevent indefinite hangs.
Description check ✅ Passed The description includes a detailed summary, test plan, and explanation of the fix, but lacks several required template sections like Related Issues and Type of Change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch workspace-create-fallback

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch

Thank you for your contribution! 🎉

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts (1)

9-9: ⚡ Quick win

Extract GENERATE_TIMEOUT_MS to a shared constant.

GENERATE_TIMEOUT_MS = 5_000 is defined identically in both ai-branch-name.ts and ai-workspace-names.ts. A future timeout change needs to be applied in two places. Extract it to a single constants.ts in the same utils/ 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

📥 Commits

Reviewing files that changed from the base of the PR and between bb5e8e5 and 337fd2d.

📒 Files selected for processing (2)
  • packages/host-service/src/trpc/router/workspace-creation/utils/ai-branch-name.ts
  • packages/host-service/src/trpc/router/workspace-creation/utils/ai-workspace-names.ts

Comment on lines +39 to +54
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,
),
),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

Comment on lines +85 to +98
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,
),
),
]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

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.

Suggested change
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.

@saddlepaddle saddlepaddle merged commit 2c111b9 into main May 5, 2026
16 of 17 checks passed
@saddlepaddle saddlepaddle mentioned this pull request May 5, 2026
3 tasks
saddlepaddle added a commit that referenced this pull request May 5, 2026
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.
saddlepaddle added a commit that referenced this pull request May 6, 2026
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).
saddlepaddle added a commit that referenced this pull request May 6, 2026
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.
@Kitenite Kitenite deleted the workspace-create-fallback branch May 6, 2026 04:50
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