Skip to content

feat(desktop): add auto-run toggle for agent task launches#2271

Merged
AviPeltz merged 4 commits into
mainfrom
task-fix-multi
Mar 9, 2026
Merged

feat(desktop): add auto-run toggle for agent task launches#2271
AviPeltz merged 4 commits into
mainfrom
task-fix-multi

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Mar 9, 2026

Summary

  • Adds a localStorage-persisted "Auto-run command" toggle to both the RunInWorkspacePopover (batch) and OpenInWorkspace (single task) components
  • When ON (default): agent commands auto-execute in the terminal / chat message auto-sends — same behavior as before
  • When OFF: terminal agents stage the command in the terminal without pressing Enter; chat agents pre-fill the input with @task:SLUG so the user can review and send manually
  • Decouples noExecute from task file existence — it's now driven by the autoExecute flag on the launch config
  • Writes task prompt .md files to the workspace via the terminal router (moved from client-side)
  • Single agentAutoRun localStorage key shared between both components for consistent preference

Test plan

  • Toggle ON + terminal agent → command auto-executes in terminal
  • Toggle OFF + terminal agent → command staged in terminal, user presses Enter
  • Toggle ON + superset chat → message auto-sends
  • Toggle OFF + superset chat → @task:SLUG pre-filled in chat input, user sends manually
  • Preference persists across popover opens and page navigations
  • Batch run (multiple tasks) respects the toggle

Summary by cubic

Adds an Auto-run command toggle for agent task launches in desktop. When off, terminal commands are staged and chat input is drafted for review; adds a batch Run in Workspace popover and switches terminal agents to file-based commands that write prompt files to .superset.

  • New Features

    • Local toggle persisted in localStorage (agentAutoRun), shared by single-task OpenInWorkspace and the new batch RunInWorkspace popover.
    • Batch Run popover launches multiple tasks with per-task status and respects the toggle.
    • Terminal agents write a task prompt .md to .superset via the terminal router and run a quoted file-based command; when off, the command is staged without pressing Enter.
    • Chat agents pre-fill @task:SLUG when off; when on, the initial prompt auto-sends.
  • Refactors

    • noExecute is now driven by an autoExecute flag, decoupled from task file existence.
    • Launch schemas/adapters pass autoExecute, taskPromptContent, taskPromptFileName, and chat taskSlug; added buildAgentFileCommand and chat draftInput.
    • Security: validate taskPromptFileName (no path traversal), quote file paths in $(cat ...), escape single quotes in paths to avoid shell breakage, log errors in the batch runner, and simplify file writes to overwrite.

Written for commit d1a5459. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Auto-run toggle to control automatic task execution in workspace sessions.
    • Draft mode for chat sessions: preview input without executing; draft content preloads into the prompt.
    • Support for file-based agent commands and automatic persistence of task prompts as workspace files.
  • Batch Actions

    • "Run in Workspace" popover: select project and agent, run multiple tasks with per-task status, persisted settings, and toast feedback.
  • UI

    • Tasks list and top bar updated to manage and act on selected task collections.

Decouple noExecute from task file existence and add a localStorage-persisted
toggle so users can choose between auto-running agent commands or staging
them for review. Terminal agents stage the command without pressing Enter;
chat agents pre-fill the input with @task:SLUG for manual send.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

📝 Walkthrough

Walkthrough

Adds file-backed task prompt persistence under workspace .superset, a draft/no-execute launch mode, and a bulk "Run in Workspace" UI. Propagates taskPromptContent/taskPromptFileName and noExecute/autoExecute through adapters, launch-command paths, and the terminal TRPC router; introduces RunInWorkspacePopover.

Changes

Cohort / File(s) Summary
Terminal TRPC router
apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
Added writeTaskFile helper that validates filenames, ensures workspacePath/.superset exists, and writes prompt content to workspacePath/.superset/<baseName>; extended createOrAttach input with taskPromptContent and taskPromptFileName and invokes the writer when provided. No uniqueness-retry implemented.
Agent orchestration adapters & types
apps/desktop/src/renderer/lib/agent-session-orchestrator/adapters/chat-adapter.ts, apps/desktop/src/renderer/lib/agent-session-orchestrator/adapters/terminal-adapter.ts, apps/desktop/src/renderer/lib/agent-session-orchestrator/types.ts
Chat adapter: added draft mode (autoExecute === false) switching initialPrompt/draftInput and retryCount behavior. Terminal adapter: propagates noExecute, taskPromptContent, and taskPromptFileName. Orchestrator types: added optional taskPromptContent and taskPromptFileName.
Terminal launch infra
apps/desktop/src/renderer/lib/terminal/launch-command.ts
Added noExecute, taskPromptContent, taskPromptFileName to input types; conditional command normalization based on noExecute; propagate new fields through launchCommandInPane and writeCommandInPane.
Task UI: Open in Workspace
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/.../OpenInWorkspace.tsx
Switched to file-based terminal command via buildAgentFileCommand referencing .superset/${taskPromptFileName}; attach taskPromptContent/taskPromptFileName and autoExecute to terminal payload; added an Auto-run toggle persisted to localStorage.
Tasks list / Top bar updates
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx, .../TasksTopBar/TasksTopBar.tsx
Prop changed to selectedTasks: TaskWithStatus[] (replacing numeric selectedCount); TasksTopBar computes count and renders RunInWorkspacePopover.
Run in Workspace UI (new)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/.../RunInWorkspacePopover/RunInWorkspacePopover.tsx, .../index.ts
New RunInWorkspacePopover component: select project and agent, toggle auto-run, persist selections, build per-task launch requests (chat or terminal), create/attach workspaces, launch sessions, track per-task statuses, and surface toasts. Re-export added via index.ts.
Chat pane draft wiring
apps/desktop/src/renderer/screens/.../ChatMastraInterface/ChatMastraInterface.tsx, apps/desktop/src/shared/tabs-types.ts
Added draftInput?: string to ChatMastraLaunchConfig and pass initialLaunchConfig?.draftInput to PromptInputProvider to support draft-mode prefill.
Shared agent command & schemas
packages/shared/src/agent-command.ts, packages/shared/src/agent-launch.ts
Added AGENT_FILE_COMMANDS and buildAgentFileCommand to construct agent-specific file commands. Extended terminalLaunchConfigSchema and chatLaunchConfigSchema with taskPromptContent, taskPromptFileName, autoExecute, and taskSlug where applicable.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as RunInWorkspacePopover
    participant Orchestrator as AgentOrchestrator
    participant Backend as TerminalTRPC
    participant Terminal as TerminalPane
    participant Chat as ChatManager

    User->>UI: Select tasks, agent, project, auto-run (or draft)
    UI->>Orchestrator: buildLaunchRequest(task, agent, autoRun)
    Orchestrator->>Backend: createWorkspace (if needed)
    Backend-->>Orchestrator: workspaceId / existing workspace
    alt chat launch
        Orchestrator->>Chat: launchAgentSession(chatConfig{initialPrompt/draftInput, autoExecute, taskSlug})
        Chat-->>Orchestrator: session created / result
    else terminal launch
        Orchestrator->>Backend: createOrAttach(terminalConfig{taskPromptContent, taskPromptFileName, autoExecute})
        Backend-->>Orchestrator: wrote file / terminal attached
        Orchestrator->>Terminal: launchCommandInPane(noExecute, command, task files)
        Terminal-->>Orchestrator: command written / executed (or not executed if noExecute)
    end
    Orchestrator->>UI: update per-task status
    UI->>User: display final statuses
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Possibly related PRs

Poem

🐰 I tuck prompts in .superset snug and tight,

Tasks line up ready, some for day, some night.
Drafts wait patient — don't press run just yet,
Batches hop through workspaces with nary a fret.
A tiny rabbit cheers: "Jobs launched just right!"

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main feature: adding an auto-run toggle for agent task launches, which aligns with the primary changeset focus.
Description check ✅ Passed The description covers the key aspects: feature summary, test plan with checkmarks, and detailed implementation notes. However, it lacks explicit sections for Related Issues and Type of Change as specified in the template.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch task-fix-multi

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.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 14 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/lib/terminal/launch-command.ts">

<violation number="1" location="apps/desktop/src/renderer/lib/terminal/launch-command.ts:57">
P1: When `noExecute` is true, the command is passed through without stripping any existing trailing `\n`. If a caller passes a command that already ends with `\n`, the terminal will still execute it, silently defeating the purpose of the flag. Strip trailing newlines in the `noExecute` path to make the behavior robust regardless of input.</violation>
</file>

<file name="packages/shared/src/agent-command.ts">

<violation number="1" location="packages/shared/src/agent-command.ts:99">
P1: File paths containing spaces will break these commands. Inside `$(cat ${filePath})`, the path is subject to shell word-splitting even though the outer result is double-quoted. Wrap the path in single quotes: `"$(cat '${filePath}')"` across all entries.</violation>
</file>

<file name="apps/desktop/src/lib/trpc/routers/terminal/terminal.ts">

<violation number="1" location="apps/desktop/src/lib/trpc/routers/terminal/terminal.ts:96">
P1: Path traversal: `taskPromptFileName` is not sanitised, so a value like `../../evil.sh` escapes the `.superset/` directory and writes to an arbitrary path. Apply the same `/`, `\`, `..` check that `SAFE_ID` already enforces, or use `path.basename()` to strip directory components.</violation>
</file>

<file name="apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx">

<violation number="1" location="apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx:235">
P2: Log the caught error so failed workspace creations can be diagnosed. Without this, failures are visible in the UI but the root cause is lost.

(Based on your team's feedback about avoiding empty catch blocks that hide failures.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

write,
noExecute,
}: WriteCommandInPaneOptions): Promise<void> {
const data = noExecute ? command : normalizeTerminalCommand(command);
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 9, 2026

Choose a reason for hiding this comment

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

P1: When noExecute is true, the command is passed through without stripping any existing trailing \n. If a caller passes a command that already ends with \n, the terminal will still execute it, silently defeating the purpose of the flag. Strip trailing newlines in the noExecute path to make the behavior robust regardless of input.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/lib/terminal/launch-command.ts, line 57:

<comment>When `noExecute` is true, the command is passed through without stripping any existing trailing `\n`. If a caller passes a command that already ends with `\n`, the terminal will still execute it, silently defeating the purpose of the flag. Strip trailing newlines in the `noExecute` path to make the behavior robust regardless of input.</comment>

<file context>
@@ -46,10 +52,12 @@ export async function writeCommandInPane({
 	write,
+	noExecute,
 }: WriteCommandInPaneOptions): Promise<void> {
+	const data = noExecute ? command : normalizeTerminalCommand(command);
 	await write({
 		paneId,
</file context>
Suggested change
const data = noExecute ? command : normalizeTerminalCommand(command);
const data = noExecute ? command.replace(/\n+$/, "") : normalizeTerminalCommand(command);
Fix with Cubic

Comment thread packages/shared/src/agent-command.ts
Comment thread apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
- Security: validate taskPromptFileName has no path traversal (../ or separators)
- Fix TOCTOU race in writeTaskFile using exclusive write flag (wx)
- Add max attempt bound (100) to prevent infinite loop
- Use unique HTML ids for Switch components to avoid DOM conflicts
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 9, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ✅ Neon database branch
  • ✅ Electric Fly.io app

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 (5)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx (3)

235-242: Consider logging the caught error for debugging.

The error is discarded silently. While the UI shows "failed" status, logging the error would aid debugging.

♻️ Suggested improvement
-			} catch {
+			} catch (err) {
+				console.error(`Failed to create workspace for task ${task.slug}:`, err);
 				setTaskStatuses((prev) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
around lines 235 - 242, The catch block in RunInWorkspacePopover (where
setTaskStatuses is called and failCount is incremented) currently swallows
errors; modify the catch to accept the error (e.g., catch (err)) and log it
using the existing logger or console (include task.id and contextual message)
before calling setTaskStatuses and incrementing failCount so failures are
recorded for debugging.

98-98: Abort mechanism is scaffolded but not wired.

abortRef is checked in the loop (line 184) but never set to true. If cancellation is intended, consider adding a cancel button that sets abortRef.current = true. If not needed now, consider removing the unused abort logic to reduce confusion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
at line 98, The abortRef used in RunInWorkspacePopover is never set so the
cancellation check in the loop (where abortRef is inspected) is ineffective;
either wire up a cancellation control (e.g., add a Cancel button or similar UI
inside RunInWorkspacePopover that sets abortRef.current = true when invoked and
ensure its handler is reachable where the popover runs) or remove the unused
abortRef and related conditional checks to avoid dead code; update any related
state/cleanup in the run handler (e.g., stop triggering further iterations or
close the popover) when abortRef is set.

47-58: Minor: BatchStatusIcon should be in its own file.

As per coding guidelines, maintain one component per file. Consider moving BatchStatusIcon to BatchStatusIcon/BatchStatusIcon.tsx with a barrel export.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
around lines 47 - 58, The BatchStatusIcon component is defined inline and should
be moved to its own file to follow the one-component-per-file guideline; create
a new component file named BatchStatusIcon/BatchStatusIcon.tsx that exports the
BatchStatusIcon function/component (preserving the same props and return UI for
statuses "pending"|"creating"|"done"|"failed"), update the original
RunInWorkspacePopover file to import BatchStatusIcon from the new barrel export
(e.g., BatchStatusIcon/index.ts exports BatchStatusIcon), and ensure any used
icons (LuCircle, Spinner, HiCheck, HiXMark) and types (TaskStatus) imports are
included in the new file and tests/build still pass.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx (2)

94-137: Consider extracting shared buildLaunchRequest logic.

The buildLaunchRequest function and taskInput construction are nearly identical to the implementation in RunInWorkspacePopover.tsx (lines 120-166). Consider extracting this to a shared utility to reduce duplication and ensure consistency.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/`$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx
around lines 94 - 137, The buildLaunchRequest duplication should be refactored
into a shared utility: extract the logic that constructs taskInput and returns
AgentLaunchRequest into a reusable function (e.g., buildAgentLaunchRequest or
createLaunchRequest) and replace the local buildLaunchRequest in this file and
the near-duplicate implementation in RunInWorkspacePopover.tsx with calls to
that util; the util should accept the task object, selectedAgent, autoRun (and
workspaceId when needed) and reuse buildAgentTaskPrompt and
buildAgentFileCommand to produce the same "chat" or "terminal" shapes so both
components remain consistent.

120-130: Consider adding defensive validation of task.slug before embedding in file path.

While task slugs are currently constrained to safe characters (locally generated slugs use [a-z0-9\-] only, and Linear identifiers follow KEY-### format), the unquoted filePath in the command substitution $(cat ${filePath}) is a theoretically vulnerable pattern. If a new slug source is added without equivalent sanitization in the future, this could enable injection. Consider explicitly validating or escaping the slug value here, or using quoted expansion $(cat "${filePath}") for additional safety.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/`$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx
around lines 120 - 130, The code builds a file path using task.slug
(taskPromptFileName) and passes it into buildAgentFileCommand, which may allow
injection if slug contains unsafe characters; update the OpenInWorkspace logic
to defensively validate or sanitize task.slug before constructing
taskPromptFileName (e.g., allow only [a-z0-9-] or replace/encode any disallowed
chars) and ensure buildAgentFileCommand receives a filePath that will be safely
quoted when expanded (i.e., use a quoted expansion pattern or otherwise
escape/encode the path); locate the construction of taskPromptFileName and the
call to buildAgentFileCommand in OpenInWorkspace.tsx and apply the
validation/escaping there (also ensure any downstream command generation in
buildAgentFileCommand treats filePath as a literal, not unquoted input).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts`:
- Around line 127-137: The call to writeTaskFile (in the handler that currently
checks workspacePath, input.taskPromptContent, and input.taskPromptFileName)
ignores its return value so the command is built using the original filename and
will fail on collisions; capture the returned finalFileName from writeTaskFile
and use that finalFileName when constructing or returning the command (or return
finalFileName to the client so it can update the command), ensuring the handler
passes workspacePath, input.taskPromptFileName, input.taskPromptContent to
writeTaskFile and then replaces usages of the original filename with the
returned finalFileName.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`:
- Around line 149-159: The code builds a file path using task.slug
(taskPromptFileName) and passes it to buildAgentFileCommand inside
RunInWorkspacePopover, which mirrors the shell-injection risk noted in
OpenInWorkspace.tsx; replace this ad-hoc interpolation with a shared, sanitized
path builder utility (e.g., sanitizeTaskFileName or buildSafeAgentFilePath) that
validates/normalizes task.slug to a safe subset of characters (or encodes it),
then use that utility in both RunInWorkspacePopover and OpenInWorkspace to
produce `.superset/{safeName}` before calling buildAgentFileCommand so no
unsanitized slug reaches the command builder.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/`$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx:
- Around line 94-137: The buildLaunchRequest duplication should be refactored
into a shared utility: extract the logic that constructs taskInput and returns
AgentLaunchRequest into a reusable function (e.g., buildAgentLaunchRequest or
createLaunchRequest) and replace the local buildLaunchRequest in this file and
the near-duplicate implementation in RunInWorkspacePopover.tsx with calls to
that util; the util should accept the task object, selectedAgent, autoRun (and
workspaceId when needed) and reuse buildAgentTaskPrompt and
buildAgentFileCommand to produce the same "chat" or "terminal" shapes so both
components remain consistent.
- Around line 120-130: The code builds a file path using task.slug
(taskPromptFileName) and passes it into buildAgentFileCommand, which may allow
injection if slug contains unsafe characters; update the OpenInWorkspace logic
to defensively validate or sanitize task.slug before constructing
taskPromptFileName (e.g., allow only [a-z0-9-] or replace/encode any disallowed
chars) and ensure buildAgentFileCommand receives a filePath that will be safely
quoted when expanded (i.e., use a quoted expansion pattern or otherwise
escape/encode the path); locate the construction of taskPromptFileName and the
call to buildAgentFileCommand in OpenInWorkspace.tsx and apply the
validation/escaping there (also ensure any downstream command generation in
buildAgentFileCommand treats filePath as a literal, not unquoted input).

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`:
- Around line 235-242: The catch block in RunInWorkspacePopover (where
setTaskStatuses is called and failCount is incremented) currently swallows
errors; modify the catch to accept the error (e.g., catch (err)) and log it
using the existing logger or console (include task.id and contextual message)
before calling setTaskStatuses and incrementing failCount so failures are
recorded for debugging.
- Line 98: The abortRef used in RunInWorkspacePopover is never set so the
cancellation check in the loop (where abortRef is inspected) is ineffective;
either wire up a cancellation control (e.g., add a Cancel button or similar UI
inside RunInWorkspacePopover that sets abortRef.current = true when invoked and
ensure its handler is reachable where the popover runs) or remove the unused
abortRef and related conditional checks to avoid dead code; update any related
state/cleanup in the run handler (e.g., stop triggering further iterations or
close the popover) when abortRef is set.
- Around line 47-58: The BatchStatusIcon component is defined inline and should
be moved to its own file to follow the one-component-per-file guideline; create
a new component file named BatchStatusIcon/BatchStatusIcon.tsx that exports the
BatchStatusIcon function/component (preserving the same props and return UI for
statuses "pending"|"creating"|"done"|"failed"), update the original
RunInWorkspacePopover file to import BatchStatusIcon from the new barrel export
(e.g., BatchStatusIcon/index.ts exports BatchStatusIcon), and ensure any used
icons (LuCircle, Spinner, HiCheck, HiXMark) and types (TaskStatus) imports are
included in the new file and tests/build still pass.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b5679135-07a4-4a5d-9fc5-86b5444f867e

📥 Commits

Reviewing files that changed from the base of the PR and between ac235fc and 073e53a.

📒 Files selected for processing (14)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/lib/agent-session-orchestrator/adapters/chat-adapter.ts
  • apps/desktop/src/renderer/lib/agent-session-orchestrator/adapters/terminal-adapter.ts
  • apps/desktop/src/renderer/lib/agent-session-orchestrator/types.ts
  • apps/desktop/src/renderer/lib/terminal/launch-command.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/TasksView.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/TasksTopBar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatMastraPane/ChatMastraInterface/ChatMastraInterface.tsx
  • apps/desktop/src/shared/tabs-types.ts
  • packages/shared/src/agent-command.ts
  • packages/shared/src/agent-launch.ts

Comment on lines +127 to +137
if (
workspacePath &&
input.taskPromptContent &&
input.taskPromptFileName
) {
await writeTaskFile(
workspacePath,
input.taskPromptFileName,
input.taskPromptContent,
);
}
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how taskPromptFileName is used in command generation
rg -n -B3 -A10 'taskPromptFileName' --type=ts -g '!*.test.ts' | head -80

Repository: superset-sh/superset

Length of output: 9500


🏁 Script executed:

rg -n 'function writeTaskFile|const writeTaskFile' --type=ts -A 20 | head -100

Repository: superset-sh/superset

Length of output: 1878


🏁 Script executed:

sed -n '27,80p' apps/desktop/src/lib/trpc/routers/terminal/terminal.ts

Repository: superset-sh/superset

Length of output: 1353


🏁 Script executed:

sed -n '147,165p' apps/desktop/src/lib/trpc/routers/terminal/terminal.ts

Repository: superset-sh/superset

Length of output: 475


🏁 Script executed:

# Check the full context of how the terminal command is constructed and used
sed -n '110,170p' apps/desktop/src/lib/trpc/routers/terminal/terminal.ts

Repository: superset-sh/superset

Length of output: 1512


🏁 Script executed:

sed -n '140,170p' apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx

Repository: superset-sh/superset

Length of output: 730


Capture and use the return value from writeTaskFile to handle filename collisions.

The writeTaskFile function implements collision handling that appends -{attempt} to filenames when they already exist (e.g., task-slug.md becomes task-slug-1.md), and it returns the actual filename written. However, the return value is discarded in the caller, while the command was pre-built using the original input filename. If a collision occurs, the command will reference a non-existent file, causing execution failure.

Store the returned finalFileName from writeTaskFile and either: (1) pass it back to the client so it can adjust the command, or (2) ensure collision handling at write time is extremely rare by using unique identifiers (e.g., timestamp, UUID) instead of simple collision counters.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/lib/trpc/routers/terminal/terminal.ts` around lines 127 -
137, The call to writeTaskFile (in the handler that currently checks
workspacePath, input.taskPromptContent, and input.taskPromptFileName) ignores
its return value so the command is built using the original filename and will
fail on collisions; capture the returned finalFileName from writeTaskFile and
use that finalFileName when constructing or returning the command (or return
finalFileName to the client so it can update the command), ensuring the handler
passes workspacePath, input.taskPromptFileName, input.taskPromptContent to
writeTaskFile and then replaces usages of the original filename with the
returned finalFileName.

Comment on lines +149 to +159
const taskPromptFileName = `task-${task.slug}.md`;
return {
kind: "terminal",
workspaceId,
agentType: selectedAgent,
source: "open-in-workspace",
terminal: {
command: buildAgentFileCommand({
filePath: `.superset/${taskPromptFileName}`,
agent: selectedAgent,
}),
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 | 🟠 Major

Same shell injection concern as OpenInWorkspace.tsx.

The task.slug is interpolated into the file path without sanitization, creating the same potential shell injection vulnerability discussed in OpenInWorkspace.tsx. Both components should use a shared, sanitized path builder.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
around lines 149 - 159, The code builds a file path using task.slug
(taskPromptFileName) and passes it to buildAgentFileCommand inside
RunInWorkspacePopover, which mirrors the shell-injection risk noted in
OpenInWorkspace.tsx; replace this ad-hoc interpolation with a shared, sanitized
path builder utility (e.g., sanitizeTaskFileName or buildSafeAgentFilePath) that
validates/normalizes task.slug to a safe subset of characters (or encodes it),
then use that utility in both RunInWorkspacePopover and OpenInWorkspace to
produce `.superset/{safeName}` before calling buildAgentFileCommand so no
unsanitized slug reaches the command builder.

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.

🧹 Nitpick comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx (2)

235-242: Capture error for better diagnostics.

The catch block discards the error object. While the status is tracked, logging or using the error would aid debugging failed task launches.

🔧 Proposed fix
-			} catch {
+			} catch (err) {
+				console.error(`Failed to create workspace for task ${task.slug}:`, err);
 				setTaskStatuses((prev) => {
 					const next = new Map(prev);
 					next.set(task.id, "failed");
 					return next;
 				});
 				failCount++;
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
around lines 235 - 242, The catch block currently swallows the thrown error;
update it to capture the error (e.g., catch (err) or catch (error)) and log or
record it for diagnostics before updating state and incrementing failCount.
Specifically, inside RunInWorkspacePopover's try/catch where setTaskStatuses and
failCount are used, change the catch to accept an error variable and call a
logger (console.error or the app logger) with contextual info (task.id and the
error) and then perform the existing setTaskStatuses((prev) => { const next =
new Map(prev); next.set(task.id, "failed"); return next; }); and failCount++.

98-98: Unused abort mechanism.

abortRef is initialized and checked in the loop (line 184), but there's no UI control to set it to true. Users cannot cancel an in-progress batch run despite the popover preventing dismissal while running.

Consider either:

  1. Adding a cancel button that sets abortRef.current = true
  2. Removing the unused abort logic if cancellation isn't planned

Also applies to: 168-184

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
at line 98, The abort mechanism (abortRef) in RunInWorkspacePopover is unused —
either wire it up to the UI or remove it: if you want cancellation, add a
visible Cancel button (or icon) in the popover that, when clicked, sets
abortRef.current = true and ensure the running state and popover blocking logic
allow early exit and cleanup so the loop that checks abortRef actually stops;
otherwise remove abortRef and its conditional checks from the batch-run loop and
simplify the popover dismissal/disabled logic so there’s no dead cancellation
code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`:
- Around line 235-242: The catch block currently swallows the thrown error;
update it to capture the error (e.g., catch (err) or catch (error)) and log or
record it for diagnostics before updating state and incrementing failCount.
Specifically, inside RunInWorkspacePopover's try/catch where setTaskStatuses and
failCount are used, change the catch to accept an error variable and call a
logger (console.error or the app logger) with contextual info (task.id and the
error) and then perform the existing setTaskStatuses((prev) => { const next =
new Map(prev); next.set(task.id, "failed"); return next; }); and failCount++.
- Line 98: The abort mechanism (abortRef) in RunInWorkspacePopover is unused —
either wire it up to the UI or remove it: if you want cancellation, add a
visible Cancel button (or icon) in the popover that, when clicked, sets
abortRef.current = true and ensure the running state and popover blocking logic
allow early exit and cleanup so the loop that checks abortRef actually stops;
otherwise remove abortRef and its conditional checks from the batch-run loop and
simplify the popover dismissal/disabled logic so there’s no dead cancellation
code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4ee3fc2f-7a0b-4fa3-a773-6412f09e0898

📥 Commits

Reviewing files that changed from the base of the PR and between 073e53a and f483c65.

📒 Files selected for processing (2)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts

- Quote filePath in $(cat ...) to prevent word-splitting on spaces
- Log caught errors in RunInWorkspacePopover instead of silently swallowing
- Simplify writeTaskFile to overwrite instead of collision-renaming, fixing
  filename mismatch when return value was ignored
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/shared/src/agent-command.ts">

<violation number="1" location="packages/shared/src/agent-command.ts:100">
P2: Single-quoting `filePath` inside `$(cat ...)` doesn't handle paths containing single quotes, which would break the shell quoting and could allow unintended command execution. Escape single quotes in `filePath` before interpolation — the standard shell idiom is `'\''` to break out and re-enter single quoting.

The safest place is in `buildAgentFileCommand` so every agent entry benefits:</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread packages/shared/src/agent-command.ts
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: 1

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx (1)

98-98: abortRef is declared but never set to true.

The ref is checked in handleRun (line 184) but nothing ever sets abortRef.current = true. Consider either:

  1. Adding a cancel button that sets abortRef.current = true
  2. Removing the unused abort logic if cancellation isn't needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`
at line 98, abortRef is created with useRef(false) but never updated, so the
cancellation check in handleRun never triggers; either wire a cancel action to
set abortRef.current = true (e.g., add a Cancel/Close button in the
RunInWorkspacePopover that calls a handler which sets abortRef.current = true
and ensures any running loop in handleRun checks it) or remove the abortRef and
related checks in handleRun if cancellation is not supported; search for
abortRef and handleRun in RunInWorkspacePopover to apply the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/shared/src/agent-command.ts`:
- Around line 98-107: AGENT_FILE_COMMANDS builds shell commands by interpolating
filePath into "$(cat '${filePath}')" which allows a single quote in filePath to
break out and enable command injection; fix by centralizing a sanitizer (e.g.,
escapeSingleQuote or shellEscapePath) and apply it to every mapping in
AGENT_FILE_COMMANDS so that every filePath has single quotes replaced with the
safe sequence '\'' (or otherwise safely quoted), or validate/whitelist allowed
characters before interpolation; update each lambda (claude, codex, gemini,
opencode, copilot, "cursor-agent") to call that sanitizer instead of directly
interpolating filePath.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx`:
- Line 98: abortRef is created with useRef(false) but never updated, so the
cancellation check in handleRun never triggers; either wire a cancel action to
set abortRef.current = true (e.g., add a Cancel/Close button in the
RunInWorkspacePopover that calls a handler which sets abortRef.current = true
and ensures any running loop in handleRun checks it) or remove the abortRef and
related checks in handleRun if cancellation is not supported; search for
abortRef and handleRun in RunInWorkspacePopover to apply the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9ba4850d-d42e-4c24-85c7-0f8ebf182255

📥 Commits

Reviewing files that changed from the base of the PR and between f483c65 and 541f878.

📒 Files selected for processing (3)
  • apps/desktop/src/lib/trpc/routers/terminal/terminal.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/components/TasksView/components/TasksTopBar/components/RunInWorkspacePopover/RunInWorkspacePopover.tsx
  • packages/shared/src/agent-command.ts

Comment thread packages/shared/src/agent-command.ts
Escape single quotes in filePath before interpolation into $(cat '...')
to prevent shell quoting breakage on paths containing single quotes.
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.

🧹 Nitpick comments (1)
packages/shared/src/agent-command.ts (1)

98-107: Derive file-mode commands from the same agent config to avoid drift.

This adds a third place to maintain per-agent flags, and codex is already out of sync across the maps in this file. A later flag change can silently make preset, heredoc, and file launches behave differently. Consider a shared per-agent descriptor and generate both prompt/file builders from it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/agent-command.ts` around lines 98 - 107,
AGENT_FILE_COMMANDS is hardcoded separately and can drift from other per-agent
command maps (e.g., the codex flags); refactor by introducing a single per-agent
descriptor (e.g., an AGENT_DESCRIPTOR record mapping AgentType -> descriptor
object containing base command and common flags) and then derive
AGENT_FILE_COMMANDS and the prompt/heredoc builders from that descriptor (create
small helpers like generateFileCommand(descriptor, filePath) and
generatePromptCommand(descriptor, prompt) to build the final strings), ensuring
the codex descriptor contains the single source of truth for its flags so all
generated commands stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/shared/src/agent-command.ts`:
- Around line 98-107: AGENT_FILE_COMMANDS is hardcoded separately and can drift
from other per-agent command maps (e.g., the codex flags); refactor by
introducing a single per-agent descriptor (e.g., an AGENT_DESCRIPTOR record
mapping AgentType -> descriptor object containing base command and common flags)
and then derive AGENT_FILE_COMMANDS and the prompt/heredoc builders from that
descriptor (create small helpers like generateFileCommand(descriptor, filePath)
and generatePromptCommand(descriptor, prompt) to build the final strings),
ensuring the codex descriptor contains the single source of truth for its flags
so all generated commands stay in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eed3a55f-7ce5-4d29-abc0-4b57867008a4

📥 Commits

Reviewing files that changed from the base of the PR and between 541f878 and d1a5459.

📒 Files selected for processing (1)
  • packages/shared/src/agent-command.ts

@AviPeltz AviPeltz merged commit 6e0b5f2 into main Mar 9, 2026
15 checks passed
@Kitenite Kitenite deleted the task-fix-multi branch March 15, 2026 16:10
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