Skip to content

stuff#53

Merged
Kitenite merged 4 commits intomainfrom
stuff
Nov 9, 2025
Merged

stuff#53
Kitenite merged 4 commits intomainfrom
stuff

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Nov 9, 2025

  • create pr button
  • start command

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

Summary by CodeRabbit

  • Bug Fixes

    • Planning status now displays as "In Progress" instead of "Pending"
  • New Features

    • Task title/description are included in processing with proper escaping
    • Optimistic tab creation: new tabs appear and become active immediately after creation
  • Changes

    • Terminal creation now starts the terminal process immediately when a command is provided
    • Automatic delayed startup command propagation to terminals removed
  • Chores

    • Workspace update flow adjusted to reflect optimistic tab updates rather than waiting for full reload

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 9, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replace onReload with onTabCreated across PlanView components to enable optimistic tab creation and immediate tab selection; TaskCard now builds and shell-escapes a dynamic Claude prompt and uses result.tab to derive newTabId; terminal tabs are spawned immediately via dynamic tmux-manager import; Terminal no longer receives an initial command prop; status label "planning" → "In Progress".

Changes

Cohort / File(s) Summary
PlanView — task prompt & tab creation
apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx, apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx
Build taskPrompt from title+description, escape for shell (backslashes/quotes/newlines), call claude with escaped prompt; require result.tab and derive newTabId; set task to planning and propagate new tab via onTabCreated; change status label for planning from "Pending" to "In Progress".
PlanView — API & prop wiring
apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx, .../PlanView/TaskPage.tsx, .../PlanView/KanbanColumn.tsx, .../NewLayout/NewLayoutMain.tsx
Replace public onReload: () => void with onTabCreated: (worktreeId: string, tab: Tab) => void and thread Tab type into imports; update call sites to accept/forward onTabCreated; NewLayoutMain implements optimistic local update handler to add/select created tab immediately.
Terminal creation & spawning
apps/desktop/src/main/lib/workspace/tab-operations.ts
When creating a terminal tab that has a command, dynamically import tmux-manager and call its create(tabId, cwd, command, cols=80, rows=30) to spawn a terminal process immediately as a side-effect.
Renderer — Terminal lifecycle & props removed
apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx, apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx
Remove delayed startup effect that executed tab.command via IPC; delete terminalCreatedRef; remove command prop from Terminal and stop sending command in terminal-create IPC payload.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant TaskCard
    participant Escaper as Sanitizer
    participant IPC
    participant Backend as WorkspaceAPI
    participant UI as Parent (NewLayoutMain)

    User->>TaskCard: Start task (title + description)
    TaskCard->>Escaper: build & escape prompt
    Escaper-->>TaskCard: escapedPrompt
    TaskCard->>IPC: create tab ("claude [escapedPrompt]")
    IPC->>Backend: create tab -> returns result (success, tab)
    alt result.success && result.tab
        TaskCard->>Backend: mark task as planning
        TaskCard->>UI: onTabCreated(worktreeId, tab)  %% optimistic update
        UI-->>TaskCard: selectTab(newTabId)  %% immediate selection
    end
Loading
sequenceDiagram
    participant UI as Main Process (tab-operations)
    participant Tmux as tmux-manager (dynamic)
    participant TerminalProc as Terminal Process

    UI->>Tmux: dynamic import tmux-manager
    UI->>Tmux: create(tabId, cwd, command, cols=80, rows=30)
    Tmux->>TerminalProc: spawn terminal attached to tabId
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Attention points:
    • TaskCard escaping logic: correctness for nested quotes, multiline, and shell edge cases.
    • Prop/API surface changes in PlanView and TaskPage — ensure all call sites pass correct args and Typescript signatures align.
    • NewLayoutMain optimistic update: race conditions between optimistic state and backend-confirmed state/reloads.
    • tab-operations dynamic import and tmux.create parameters and error handling.
    • Verify removal of Terminal command prop doesn't break other consumers or tests.

Possibly related PRs

  • stuff #53 — Matches changes to PlanView/TaskCard, terminal startup removal, and tmux spawn behavior; likely overlapping implementation.
  • Plan view feature #37 — Modifies the same PlanView components and API (onReload → onTabCreated); probable overlap/conflict.

Poem

🐰 I stitched a prompt from title and thread,
Escaped each quote, each newline gently fed,
Tabs bloom quick — no waiting line,
Terminals spawn and hum in time,
"In Progress" hops where "Pending" led.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description contains only two bullet points ('create pr button' and 'start command') with no substantive content in any template sections, leaving most required information absent. Expand the description with details about what changed and why, fill in the Type of Change checkbox, describe testing performed, and explain the impact of these modifications.
Title check ❓ Inconclusive The title 'stuff' is vague and generic, using a non-descriptive term that conveys no meaningful information about the changeset. Replace 'stuff' with a clear, descriptive title that captures the main change, such as 'Move terminal command execution to tab creation' or 'Refactor terminal startup logic'.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 26fe60c and 142c07f.

📒 Files selected for processing (5)
  • apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/KanbanColumn.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/PlanView.tsx (8 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx (4 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/TaskPage.tsx (4 hunks)

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

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/screens/main/components/PlanView/TaskCard.tsx (1)

111-113: Consider more robust synchronization instead of setTimeout.

The 100ms delay is a workaround for ensuring workspace data is reloaded before selecting the tab. This could fail if the reload takes longer or succeed with unnecessary delay.

Consider options:

  1. Have onReload() return a promise you can await before calling onTabSelect
  2. Make onTabSelect handle cases where the tab isn't immediately available
  3. If the current approach is reliable in practice, add a comment explaining the timing dependency
 				// Reload workspace to get updated tab data
 				await onReload();

 				// Select the new tab after reload
 				const newTabId = result.tab?.id;
 				if (newTabId) {
-					// Small delay to ensure workspace is reloaded
+					// Wait for workspace reload to propagate to UI state
 					setTimeout(() => {
 						onTabSelect(targetWorktreeId, newTabId);
 					}, 100);
 				}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 80cfa65 and 0b01a7e.

📒 Files selected for processing (2)
  • apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Unit Test
🔇 Additional comments (1)
apps/desktop/src/renderer/screens/main/components/PlanView/TaskDetailModal.tsx (1)

39-39: LGTM! Better semantic accuracy.

The label change from "Pending" to "In Progress" more accurately reflects that planning is an active state. This aligns well with the workflow where starting a task (in TaskCard.tsx) sets the status to "planning".

Comment on lines +82 to +93
const taskPrompt = `${task.title}\n\n${task.description || ""}`.trim();
// Escape quotes and newlines for shell command
const escapedPrompt = taskPrompt
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Escape double quotes
.replace(/\n/g, '\\n'); // Escape newlines
const result = await window.ipcRenderer.invoke("tab-create", {
workspaceId: currentWorkspace.id,
worktreeId: targetWorktreeId,
name: `Task: ${task.slug}`,
type: "terminal",
command: `claude "hi"`,
command: `claude "${escapedPrompt}"`,
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 | 🔴 Critical

Critical: Incomplete shell escaping enables command injection.

The current escaping logic only handles backslashes, double quotes, and newlines. It's missing critical shell metacharacters that are interpreted within double quotes:

  • $ → variable expansion: ${task.title} containing $HOME would expand
  • ` → command substitution: backticks allow arbitrary command execution
  • ! → history expansion (in some shells with histexpand enabled)

If a task title or description contains these characters, they will be executed in the shell context, allowing arbitrary code execution.

Recommended fix: Use a safer approach by passing the prompt as a separate argument or using proper shell escaping:

-		try {
-			// Create a new terminal with claude command
-			const taskPrompt = `${task.title}\n\n${task.description || ""}`.trim();
-			// Escape quotes and newlines for shell command
-			const escapedPrompt = taskPrompt
-				.replace(/\\/g, '\\\\')  // Escape backslashes first
-				.replace(/"/g, '\\"')     // Escape double quotes
-				.replace(/\n/g, '\\n');   // Escape newlines
+		try {
+			// Create a new terminal with claude command
+			const taskPrompt = `${task.title}\n\n${task.description || ""}`.trim();
+			// Escape for shell: backslashes, quotes, dollar signs, backticks, and newlines
+			const escapedPrompt = taskPrompt
+				.replace(/\\/g, '\\\\')   // Escape backslashes first
+				.replace(/"/g, '\\"')      // Escape double quotes
+				.replace(/\$/g, '\\$')     // Escape dollar signs
+				.replace(/`/g, '\\`')      // Escape backticks
+				.replace(/!/g, '\\!')      // Escape exclamation marks
+				.replace(/\n/g, '\\n');    // Escape newlines
 			const result = await window.ipcRenderer.invoke("tab-create", {

Better alternative: If the IPC handler supports it, consider passing the prompt as a separate parameter rather than embedding it in the command string, avoiding shell escaping entirely.

📝 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 taskPrompt = `${task.title}\n\n${task.description || ""}`.trim();
// Escape quotes and newlines for shell command
const escapedPrompt = taskPrompt
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Escape double quotes
.replace(/\n/g, '\\n'); // Escape newlines
const result = await window.ipcRenderer.invoke("tab-create", {
workspaceId: currentWorkspace.id,
worktreeId: targetWorktreeId,
name: `Task: ${task.slug}`,
type: "terminal",
command: `claude "hi"`,
command: `claude "${escapedPrompt}"`,
const taskPrompt = `${task.title}\n\n${task.description || ""}`.trim();
// Escape for shell: backslashes, quotes, dollar signs, backticks, and newlines
const escapedPrompt = taskPrompt
.replace(/\\/g, '\\\\') // Escape backslashes first
.replace(/"/g, '\\"') // Escape double quotes
.replace(/\$/g, '\\$') // Escape dollar signs
.replace(/`/g, '\\`') // Escape backticks
.replace(/!/g, '\\!') // Escape exclamation marks
.replace(/\n/g, '\\n'); // Escape newlines
const result = await window.ipcRenderer.invoke("tab-create", {
workspaceId: currentWorkspace.id,
worktreeId: targetWorktreeId,
name: `Task: ${task.slug}`,
type: "terminal",
command: `claude "${escapedPrompt}"`,
🤖 Prompt for AI Agents
In apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx
around lines 82 to 93, the current escaping only handles backslashes, double
quotes and newlines leaving shell metacharacters like $, `, and ! unescaped
which allows command injection; fix by not embedding the prompt in a single
shell command string—change the ipcRenderer.invoke payload to send the prompt as
a separate field (e.g. prompt: taskPrompt) or send a command + args array so the
main process can spawn/execFile safely with the prompt as an argument, or if you
cannot change the IPC contract, implement robust shell-escaping on the renderer
side (escape $, `, ! and properly handle single quotes using the standard
close-quote-escape-quote pattern) and update the main process to use
spawn/execFile with args rather than passing a full shell command to a shell.

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

♻️ Duplicate comments (1)
apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx (1)

82-93: Critical: Command injection vulnerability remains unfixed.

The incomplete shell escaping identified in the previous review has not been addressed. The current implementation still only escapes backslashes, double quotes, and newlines, leaving critical shell metacharacters unescaped:

  • $ → variable expansion and command substitution
  • ` → command substitution
  • ! → history expansion

A task title containing these characters will be executed in the shell context, allowing arbitrary code execution.

Required fix: Apply the escaping suggested in the previous review or, preferably, refactor to pass the prompt as a separate IPC parameter rather than embedding it in a shell command string. The latter approach eliminates the need for shell escaping entirely and is more secure.

 		const escapedPrompt = taskPrompt
 			.replace(/\\/g, '\\\\')  // Escape backslashes first
 			.replace(/"/g, '\\"')     // Escape double quotes
+			.replace(/\$/g, '\\$')    // Escape dollar signs
+			.replace(/`/g, '\\`')     // Escape backticks
+			.replace(/!/g, '\\!')     // Escape exclamation marks
 			.replace(/\n/g, '\\n');   // Escape newlines
🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx (1)

377-389: LGTM - Dual reload ensures workspace consistency.

The reloadWorkspaceData function correctly refreshes both the workspace list and the current workspace, ensuring that changes (like new tabs created in TaskCard.tsx) are immediately reflected in the UI state.

Consider adding a brief comment explaining why both loads are necessary, for future maintainability:

 	// Reload both workspaces and current workspace
 	const reloadWorkspaceData = async () => {
+		// Refresh the workspace list first
 		await loadAllWorkspaces();
+		// Then refresh the current workspace to ensure tab/worktree changes are reflected
 		if (currentWorkspace) {
 			const refreshedWorkspace = await window.ipcRenderer.invoke(
 				"workspace-get",
 				currentWorkspace.id,
 			);
 			if (refreshedWorkspace) {
 				setCurrentWorkspace(refreshedWorkspace);
 			}
 		}
 	};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b01a7e and 26fe60c.

📒 Files selected for processing (5)
  • apps/desktop/src/main/lib/workspace/tab-operations.ts (1 hunks)
  • apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx (0 hunks)
  • apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx (2 hunks)
  • apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx (2 hunks)
💤 Files with no reviewable changes (2)
  • apps/desktop/src/renderer/screens/main/components/MainContent/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/MainContent/TabContent.tsx
🔇 Additional comments (2)
apps/desktop/src/renderer/screens/main/components/NewLayout/NewLayoutMain.tsx (1)

966-966: Correct integration with PlanView.

The change from loadAllWorkspaces to reloadWorkspaceData ensures that PlanView operations (especially tab creation in TaskCard) properly update both the workspace list and the current workspace state.

apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx (1)

96-110: LGTM - Immediate tab selection is safe and properly handled.

The logic correctly validates, captures the tab ID, updates status, and reloads workspace before selecting the tab. Terminal component has robust initialization handling: multiple guards (terminalIdRef, isDisposed, isInitialSetup), retry logic for container dimensions, and write queuing during resize. The tmux process creation in tab-operations.ts is awaited before tab selection occurs, eliminating race conditions. Terminal gracefully handles async initialization even if displayed early.

Comment on lines +103 to +119
// Create terminal process immediately for terminal tabs with commands
if (tab.type === "terminal" && tab.command) {
const tmuxManager = await import("../tmux-manager").then(
(m) => m.default,
);
const workingDir = worktree.path || workspace.repoPath;

// Create terminal with command - it will be executed automatically
await tmuxManager.create({
id: tab.id,
cwd: workingDir,
command: tab.command,
// Use default dimensions, Terminal component will resize when it loads
cols: 80,
rows: 30,
});
}
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 | 🔴 Critical

🧩 Analysis chain

Verify command sanitization in tmux-manager to prevent injection.

The tab.command string is passed directly to tmuxManager.create() without validation or sanitization in this layer. Based on the related changes in TaskCard.tsx (which constructs commands using incomplete shell escaping), there is a potential command injection vulnerability if:

  1. The command originates from user-controlled data (task titles/descriptions)
  2. tmux-manager executes the command in a shell context without proper escaping

Run the following script to examine how tmux-manager handles the command parameter:

If tmux-manager passes the command to a shell without additional escaping, you must either:

  • Sanitize/validate tab.command here before passing it
  • Pass the command as a structured array of arguments rather than a shell string
  • Ensure the command is properly escaped at the source (TaskCard.tsx - see related comment)

🏁 Script executed:

#!/bin/bash
# Description: Verify tmux-manager's command handling for injection risks

# Check tmux-manager implementation
ast-grep --pattern $'create($$$) {
  $$$
}'

# Also search for how command is used
rg -nP --type=ts -C5 'command.*exec|spawn.*command' apps/desktop/src/main/lib/tmux-manager

Length of output: 78



🏁 Script executed:

#!/bin/bash
# Find tmux-manager files
fd -e ts -e js "tmux-manager" apps/desktop/src/main/lib/

# Also check parent directory structure
find apps/desktop/src/main/lib -maxdepth 2 -type f -name "*tmux*" 2>/dev/null

# Search for create function calls and command usage
rg -nP --type=ts "create\s*\(" apps/desktop/src/main/lib/ -A3 | head -50

Length of output: 2356



🏁 Script executed:

#!/bin/bash
# View tmux-manager.ts create() method implementation
sed -n '197,250p' apps/desktop/src/main/lib/tmux-manager.ts

# Also search for command execution patterns (spawn, exec, shell)
rg -nP --type=ts "spawn|exec|shell" apps/desktop/src/main/lib/tmux-manager.ts -B2 -A2

Length of output: 3782



🏁 Script executed:

#!/bin/bash
# View executeCommand method implementation
sed -n '476,490p' apps/desktop/src/main/lib/tmux-manager.ts

# Also look for sendKeys or write operations
rg -nP --type=ts "sendKeys|write.*command|pty\.write" apps/desktop/src/main/lib/tmux-manager.ts -B2 -A2

Length of output: 670



🏁 Script executed:

#!/bin/bash
# View the write() method implementation
rg -nP --type=ts "^\s*write\s*\(" apps/desktop/src/main/lib/tmux-manager.ts -A15

Length of output: 510



🏁 Script executed:

#!/bin/bash
# View tab-operations.ts to see where tab.command comes from
sed -n '1,50p' apps/desktop/src/main/lib/workspace/tab-operations.ts

# Search for tab.command assignment
rg -nP --type=ts "tab\.command\s*=" apps/desktop/src/main/lib/workspace/tab-operations.ts -B3 -A1

# Check input source
sed -n '40,50p' apps/desktop/src/main/lib/workspace/tab-operations.ts

Length of output: 1963



🏁 Script executed:

#!/bin/bash
# Find TaskCard files
fd -e tsx -e ts "TaskCard" apps/desktop/src

# Search for command construction in TaskCard
rg -nP "command.*=" apps/desktop/src --type=tsx -B2 -A2 | grep -i "task\|card" -A5 -B5 | head -80

Length of output: 99



🏁 Script executed:

#!/bin/bash
# View TaskCard.tsx - search for command construction
rg -n "command" apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx -B3 -A3

# Also check for shell escaping patterns
rg -n "escape|quote|shell" apps/desktop/src/renderer/screens/main/components/PlanView/TaskCard.tsx -B2 -A2

Length of output: 1143


Verify command sanitization in tmux-manager to prevent injection — VULNERABILITY CONFIRMED.

The tab.command string is passed directly to tmuxManager.create() without validation or sanitization. The underlying vulnerability stems from incomplete shell escaping in TaskCard.tsx (lines 84-87), which only escapes backslashes, double quotes, and newlines but omits critical shell metacharacters like $, backticks (`), !, and command substitution syntax.

Execution path:

  • TaskCard.tsx constructs command with incomplete escaping
  • Command flows through input.command → tab.command → tmuxManager.create() → PTY.write()
  • tmux-manager writes directly to bash PTY (line 462) without additional escaping

Risks:

  1. User input containing $(command) or `command` bypasses current escaping and executes arbitrary code
  2. No secondary validation layer exists between command construction and shell execution

Required fix: Either properly escape all shell metacharacters using a robust method (e.g., shell escaping library or printf %q equivalent) or refactor to pass commands as argument arrays rather than shell strings.

🤖 Prompt for AI Agents
In apps/desktop/src/main/lib/workspace/tab-operations.ts around lines 103 to
119, tab.command is forwarded directly into tmuxManager.create() which passes it
to a shell/PTY without sufficient escaping, allowing shell metacharacter
injection; fix by validating and sanitizing the command before passing it:
either change the call site and tmux-manager API to accept command as an
argument array (preferred) and pass args directly to the PTY/shell without
joining into a shell string, or, if a string must be used, run the command
through a proven shell-escaping library (or implement printf %q-style quoting)
that escapes all metacharacters ($, `, !, ;, |, &, >, <, (), {}, *, ?, [, ],
etc.), and add a defensive runtime check that rejects or escapes any dangerous
constructs before invoking tmuxManager.create().

@Kitenite Kitenite merged commit 2a7ef01 into main Nov 9, 2025
1 of 5 checks passed
@Kitenite Kitenite deleted the stuff branch November 9, 2025 20:55
This was referenced Nov 11, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Nov 19, 2025
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