diff --git a/apps/backend/internal/orchestrator/event_handlers_workflow.go b/apps/backend/internal/orchestrator/event_handlers_workflow.go
index 0dfaf27c7d..7c885c5a40 100644
--- a/apps/backend/internal/orchestrator/event_handlers_workflow.go
+++ b/apps/backend/internal/orchestrator/event_handlers_workflow.go
@@ -548,6 +548,19 @@ func (s *Service) autoStartStepPrompt(
// Record a user message so the auto-start prompt is visible in chat history.
s.recordAutoStartMessage(ctx, taskID, sessionID, prompt, planMode)
+ // If the session is in CREATED state, the agent was never started (e.g. workspace-only
+ // preparation from a blocked auto-start). PromptTask will reject CREATED sessions,
+ // so use StartCreatedSession which properly launches the agent on the prepared workspace.
+ // Pass skipMessageRecord=true since recordAutoStartMessage above already recorded it.
+ if session.State == models.TaskSessionStateCreated {
+ s.logger.Info("auto-start: session is CREATED, launching agent via StartCreatedSession",
+ zap.String("task_id", taskID),
+ zap.String("session_id", sessionID),
+ zap.String("step_name", stepName))
+ _, err := s.StartCreatedSession(ctx, taskID, sessionID, session.AgentProfileID, prompt, true, planMode, nil)
+ return err
+ }
+
const maxRetryAttempts = 5
for attempt := 1; attempt <= maxRetryAttempts; attempt++ {
_, err := s.PromptTask(ctx, taskID, sessionID, prompt, "", planMode, nil)
diff --git a/apps/web/components/task/chat/message-renderer.tsx b/apps/web/components/task/chat/message-renderer.tsx
index 8963c4b64a..791dcbf8ab 100644
--- a/apps/web/components/task/chat/message-renderer.tsx
+++ b/apps/web/components/task/chat/message-renderer.tsx
@@ -7,6 +7,7 @@ import type { Message, TaskSessionState, TaskState } from "@/lib/types/http";
import type { ToolCallMetadata } from "@/components/task/chat/types";
import { launchSession } from "@/lib/services/session-launch-service";
import { buildStartCreatedRequest } from "@/lib/services/session-launch-helpers";
+import { useAppStore } from "@/components/state-provider";
import { ChatMessage } from "@/components/task/chat/messages/chat-message";
import { PermissionRequestMessage } from "@/components/task/chat/messages/permission-request-message";
import { StatusMessage } from "@/components/task/chat/messages/status-message";
@@ -39,6 +40,10 @@ type AdapterContext = {
function TaskDescriptionStartButton({ taskId, sessionId }: { taskId: string; sessionId: string }) {
const [isStarting, setIsStarting] = useState(false);
+ const prepareStatus = useAppStore(
+ (state) => state.prepareProgress.bySessionId[sessionId]?.status ?? null,
+ );
+
const handleStart = useCallback(async () => {
setIsStarting(true);
try {
@@ -51,6 +56,9 @@ function TaskDescriptionStartButton({ taskId, sessionId }: { taskId: string; ses
}
}, [taskId, sessionId]);
+ // Hide while environment is being prepared
+ if (prepareStatus === "preparing") return null;
+
return (