Skip to content

feat(desktop): Add GitHub issue attachment to new workspace modal#2678

Merged
AviPeltz merged 5 commits into
mainfrom
plan-gh-issue-attachment
Mar 21, 2026
Merged

feat(desktop): Add GitHub issue attachment to new workspace modal#2678
AviPeltz merged 5 commits into
mainfrom
plan-gh-issue-attachment

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Mar 21, 2026

Summary

Adds the ability to link and attach GitHub issues when creating new workspaces, with full issue content included as context for the AI agent.

Changes

Backend

  • Added listIssues tRPC procedure to fetch open GitHub issues via gh issue list
  • Added getIssueContent procedure to fetch full issue details via gh issue view

Frontend Components

  • IssueIcon: Displays issue state (green dot for open, purple check for closed)
  • GitHubIssueLinkCommand: Search/select issues with fuzzy search by number or title
  • LinkedGitHubIssuePill: Display linked GitHub issues with state icon
  • Extended LinkedIssue type to support both GitHub and internal Superset issues

Integration

  • Fetches and formats GitHub issue content as markdown files
  • Attaches issues to agent context in .superset/attachments/github-issue-{number}.md
  • Supports direct URL pasting for quick linking
  • Graceful error handling without blocking workspace creation

Testing

  • Open new workspace modal
  • Click "+" → "Link GitHub issue"
  • Search for issue by number or title
  • Verify issue pill displays with correct icon and "add close pane control #123 · GitHub"
  • Create workspace and verify issue content is attached
  • Verify existing "Link issue" (internal tasks) still works

Summary by cubic

Adds GitHub issue linking to the new workspace modal and attaches full issue content as a markdown file to the agent context. Improves validation, sanitization, and error handling for safer, more reliable linking.

  • New Features

    • Backend: added listIssues and getIssueContent tRPC procedures via gh CLI, scoped to the repo.
    • UI: new “Link GitHub issue” command with fuzzy search by number/title and direct URL paste; shows state via IssueIcon and LinkedGitHubIssuePill (stored as LinkedIssue with github source).
    • Integration: on workspace creation, fetch issue content and attach github-issue-{number}.md; includes per-issue 10s timeout and 50KB body truncation; failures don’t block.
  • Bug Fixes

    • Validation: zod on listIssues/getIssueContent, positive issueNumber, complete HTML entity sanitization, and URL protocol checks; normalize issue state to "open" | "closed".
    • Reliability/UX: use TRPCError with proper codes, add 10s timeouts to gh CLI calls, trim whitespace for URL-based matching, URL-based duplicate detection, fix memory leak in useCallback deps, and minor accessibility polish.

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

Summary by CodeRabbit

  • New Features
    • Search, browse, and link GitHub issues from your projects when creating a workspace.
    • Linked GitHub issues capture and include issue metadata (title, body, URL, number, state) with size-sanitization.
    • Visual open/closed status indicators and dedicated GitHub issue pills in the UI.
    • Workspace creation will attach linked issue content when present.

- Add backend tRPC procedures to list and fetch GitHub issues via gh CLI
- Create IssueIcon component for open/closed issue states
- Implement GitHubIssueLinkCommand with fuzzy search by number/title
- Add LinkedGitHubIssuePill component for displaying linked issues
- Extend LinkedIssue type to support both GitHub and internal issues
- Fetch and attach full issue content as markdown files to agent context
- Support URL pasting for quick issue linking
- Gracefully handle errors without blocking workspace creation
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 21, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Adds GitHub issue integration: two tRPC procedures to list and fetch issue content (via the gh CLI), extends linked-issue types, and implements UI components and flows to search, link, display, and attach GitHub issues during workspace creation.

Changes

Cohort / File(s) Summary
tRPC Backend
apps/desktop/src/lib/trpc/routers/projects/projects.ts
Added listIssues and getIssueContent procedures that run gh issue commands, parse JSON, validate with Zod, normalize states, and handle errors (returning [] or throwing TRPC errors as appropriate).
Linked Issue Types
apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModalDraftContext.tsx
Extended LinkedIssue with optional source, url, number, and state fields (GitHub metadata) and clarified slug format in a comment.
PromptGroup Integration
apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx
Added GitHub linking UI state/handlers, adjusted PlusMenu props to include onOpenGitHubIssue, fetches issue content during workspace creation (sanitizes/truncates and converts to base64 attachments), and renders GitHub-specific pills.
GitHub Issue Selection UI
apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx, .../index.ts
New GitHubIssueLinkCommand component: popover search using listIssues, Fuse.js search ranking, exact-URL matching, and selection callback; index re-export added.
Linked Issue Pill
apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/LinkedGitHubIssuePill/LinkedGitHubIssuePill.tsx, .../index.ts
New LinkedGitHubIssuePill component to display issue title/number and normalized open/closed state with remove handling; index re-export added.
Issue Icon
apps/desktop/src/renderer/screens/main/components/IssueIcon/IssueIcon.tsx, .../index.ts
New IssueIcon component and IssueState type to render open/closed icons with state-specific styling.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as PromptGroup / Command
    participant Client as tRPC Client
    participant Server as tRPC Server
    participant GH as GitHub CLI (gh)

    User->>UI: Open issue link popover / type query
    UI->>Client: listIssues(projectId)
    Client->>Server: tRPC request
    Server->>GH: run `gh issue list --json ...`
    GH-->>Server: JSON stdout
    Server->>Server: Parse & validate (Zod), normalize states
    Server-->>Client: issue array
    Client-->>UI: update results
    UI->>UI: Fuse.js search/filter
    User->>UI: Select issue
    UI->>Client: getIssueContent(projectId, issueNumber)
    Client->>Server: tRPC request
    Server->>GH: run `gh issue view <n> --json ...`
    GH-->>Server: JSON stdout
    Server->>Server: Parse & validate (Zod), normalize fields
    Server-->>Client: issue content
    Client-->>UI: issue content returned
    UI->>UI: sanitize/convert to attachment and add pill
Loading
sequenceDiagram
    participant User
    participant PromptGroup
    participant Client as tRPC Client
    participant Server as tRPC Server
    participant GH as GitHub CLI (gh)

    User->>PromptGroup: Click "Create Workspace"
    PromptGroup->>PromptGroup: Filter linkedIssues for GitHub ones
    loop per GitHub issue
        PromptGroup->>Client: getIssueContent(projectId, number)
        Client->>Server: tRPC request
        Server->>GH: `gh issue view`
        GH-->>Server: JSON
        Server->>Server: Parse & validate
        Server-->>Client: issue content
        Client-->>PromptGroup: issue data
        PromptGroup->>PromptGroup: sanitize, truncate, convert to base64
        PromptGroup->>PromptGroup: append convertedFiles
    end
    PromptGroup->>Server: create workspace (with attachments if any)
    Server-->>PromptGroup: workspace created
    PromptGroup-->>User: workspace ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through code to fetch each bug and plea,
I sniffed the gh CLI and brought issues to thee,
Pills and icons gleam, a fuzzy search so spry,
Attachments tucked in safely, ready to fly,
Happy rabbit hops—GitHub links live and free!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 accurately and concisely summarizes the main feature added: enabling GitHub issue attachment in the new workspace modal.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, changes, testing steps, and additional context with both auto-generated and manual sections.

✏️ 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 plan-gh-issue-attachment

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.

- Add runtime validation with zod schema for GitHub issue API responses
- Add input validation for issueNumber (must be positive integer)
- Sanitize user-generated content (title, author, body) to prevent XSS
- Fix memory leak in useCallback dependencies (remove unstable references)
Security & Validation:
- Complete HTML entity sanitization (& < > " ') to prevent XSS
- URL validation to prevent javascript: and other malicious protocols
- Add Zod validation to listIssues for consistency
- Normalize issue state types with runtime validation

Performance & Reliability:
- Add 10-second timeout per issue fetch to prevent hanging
- Limit issue body size to 50KB to prevent memory issues
- Use URL-based duplicate detection (handles cross-repo issues)

Type Safety:
- Normalize IssueState throughout the codebase
- Proper type guards for state coercion
- Consistent "open" | "closed" typing in LinkedIssue

Code Quality:
- Remove unused keyboard handlers from pill component
- Simplify accessibility implementation
@AviPeltz AviPeltz marked this pull request as ready for review March 21, 2026 03:43
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 9 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/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx">

<violation number="1" location="apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx:906">
P2: GitHub issue identity is inconsistent: items are deduped by URL but keyed/removed by numeric slug, so same-number issues from different repos collide.</violation>
</file>

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

state.toLowerCase() === "closed" ? "closed" : "open";

const issue = {
slug: `#${issueNumber}`,
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

P2: GitHub issue identity is inconsistent: items are deduped by URL but keyed/removed by numeric slug, so same-number issues from different repos collide.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx, line 906:

<comment>GitHub issue identity is inconsistent: items are deduped by URL but keyed/removed by numeric slug, so same-number issues from different repos collide.</comment>

<file context>
@@ -762,6 +892,29 @@ function PromptGroupInner({
+			state.toLowerCase() === "closed" ? "closed" : "open";
+
+		const issue = {
+			slug: `#${issueNumber}`,
+			title,
+			source: "github" as const,
</file context>
Fix with Cubic

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: 6

🤖 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/projects/projects.ts`:
- Around line 433-435: Replace the generic Error throws in getIssueContent with
TRPCError instances so tRPC clients receive structured errors; specifically,
change the Project-not-found throw in getIssueContent (currently "throw new
Error(`Project ${input.projectId} not found`)") to throw new TRPCError with an
appropriate code (e.g., 'NOT_FOUND') and a clear message, and likewise replace
the other generic throws around lines 480–482 with TRPCError using suitable
codes (e.g., 'NOT_FOUND' or 'INTERNAL') so error handling is consistent; locate
these in the getIssueContent resolver and update the throw sites to use
TRPCError({ code: ..., message: ... }) instead of new Error(...).

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx`:
- Around line 79-88: The exact URL match against searchQuery can fail if the
user pasted a URL with leading/trailing whitespace; trim searchQuery before
checking urlMatch. In the GitHubIssueLinkCommand component, normalize the query
by calling trim() (e.g., const normalizedQuery = searchQuery.trim()) and use
normalizedQuery for the urlMatch lookup and for the issueFuse.search call while
preserving the early-return for empty queries (use normalizedQuery when testing
emptiness and matching against issuesWithSearchField and issueFuse, keeping
MAX_RESULTS unchanged).

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/LinkedGitHubIssuePill/LinkedGitHubIssuePill.tsx`:
- Around line 33-38: The remove-button is only revealed for pointer hover via
the "group-hover" utilities, which breaks keyboard accessibility; update the
Button rendered in LinkedGitHubIssuePill (the Button with aria-label "Remove
linked issue" and its className) to also include the matching
"group-focus-within" utilities (e.g., add group-focus-within:opacity-100 and
group-focus-within:pointer-events-auto alongside the existing
group-hover:opacity-100 and group-hover:pointer-events-auto) so the control
becomes visible and interactive when its parent group receives keyboard focus.

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx`:
- Around line 905-915: The slug currently set to `#${issueNumber}` collapses
issues from different repos; change the slug on the created `issue` object to a
repo-scoped identifier (e.g. use `url` or `\`${owner}/${repo}#${issueNumber}\``)
instead of `#${issueNumber}` so it’s unique per repo, and update any logic that
relies on `issue.slug` (the linkedIssues list, the remove-by-id handler, and the
React key usage that references `issue.slug`) to use this new repo-scoped id so
rendering and deletion remain correct; ensure `updateDraft({ linkedIssues:
[...linkedIssues, issue] })` still pushes the new issue with the updated slug.
- Around line 683-691: The code re-resolves issues using { projectId,
issue.number } which can return wrong or 404 for cross-repo links; instead parse
and use the repo-scoped identifier from the stored issue (e.g. issue.url or
stored owner/repo) and call the repo-scoped API rather than
utils.client.projects.getIssueContent.query with projectId. Concretely: in the
githubIssues loop (githubIssues, issue.url, issue.number, fetchWithTimeout,
utils.client.projects.getIssueContent.query), extract owner and repo from
issue.url (or use the persisted owner/repo field), then call the repo-specific
endpoint (pass owner, repo, issueNumber) to fetch issue content; add a safe
fallback/log and timeout handling if parsing fails. Ensure you stop using
projectId for resolving cross-repo issue content.
- Around line 667-680: The renderer adds a 10s Promise.race timeout
(fetchWithTimeout) but the backend call using execWithShellEnv still runs gh
issue view with no child-process timeout; update the backend execWithShellEnv
implementation to accept an optional timeout parameter and pass it through to
Node's execFile/child_process invocation (use the execFile timeout option) so
the spawned gh process is killed if it exceeds the timeout, then update the
caller that runs the "gh issue view" command to supply the same 10_000ms timeout
when invoking execWithShellEnv.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36648d10-2913-4f67-9799-a6f915a39930

📥 Commits

Reviewing files that changed from the base of the PR and between f012bf1 and 3a98340.

📒 Files selected for processing (9)
  • apps/desktop/src/lib/trpc/routers/projects/projects.ts
  • apps/desktop/src/renderer/components/NewWorkspaceModal/NewWorkspaceModalDraftContext.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/GitHubIssueLinkCommand/GitHubIssueLinkCommand.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/GitHubIssueLinkCommand/index.ts
  • apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/LinkedGitHubIssuePill/LinkedGitHubIssuePill.tsx
  • apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/components/LinkedGitHubIssuePill/index.ts
  • apps/desktop/src/renderer/screens/main/components/IssueIcon/IssueIcon.tsx
  • apps/desktop/src/renderer/screens/main/components/IssueIcon/index.ts

Comment thread apps/desktop/src/lib/trpc/routers/projects/projects.ts
Comment on lines +667 to +680
// Helper to add timeout to promises
const fetchWithTimeout = <T,>(
promise: Promise<T>,
timeoutMs: number,
): Promise<T> => {
return Promise.race([
promise,
new Promise<T>((_, reject) =>
setTimeout(
() => reject(new Error("Request timeout")),
timeoutMs,
),
),
]);
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

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd 'projects\.ts$' | xargs -r sed -n '/getIssueContent/,/^[[:space:]]*}),/p'
echo '----'
rg -n --type ts --type tsx -C3 'getIssueContent|gh issue view|timeout|AbortSignal|abort|kill|execFile|spawn'

Repository: superset-sh/superset

Length of output: 497


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and read the full getIssueContent implementation
echo "=== Searching for getIssueContent implementation ==="
find . -name "*.ts" -o -name "*.tsx" | xargs rg -l "getIssueContent" | head -5

echo ""
echo "=== Full getIssueContent procedure ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec rg -A 50 "getIssueContent.*publicProcedure" {} + | head -100

echo ""
echo "=== Searching for gh issue view calls ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec rg -B 5 -A 10 "gh issue view" {} +

echo ""
echo "=== Searching for timeout/AbortSignal patterns ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) -exec rg -B 3 -A 5 "timeout|AbortSignal|AbortController" {} + | head -80

Repository: superset-sh/superset

Length of output: 271


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Read the projects.ts file to find getIssueContent implementation
echo "=== Full projects.ts content (or relevant sections) ==="
wc -l ./apps/desktop/src/lib/trpc/routers/projects/projects.ts

echo ""
echo "=== getIssueContent procedure with full context ==="
rg -B 5 -A 80 "getIssueContent.*publicProcedure" ./apps/desktop/src/lib/trpc/routers/projects/projects.ts

Repository: superset-sh/superset

Length of output: 2483


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Search for execWithShellEnv definition ==="
rg -B 3 -A 30 "^.*execWithShellEnv.*=|function execWithShellEnv|const execWithShellEnv" --type ts --type tsx | head -100

echo ""
echo "=== Check where execWithShellEnv is imported from ==="
rg "from.*execWithShellEnv|import.*execWithShellEnv" ./apps/desktop/src/lib/trpc/routers/projects/projects.ts

Repository: superset-sh/superset

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Check imports in projects.ts ==="
head -50 ./apps/desktop/src/lib/trpc/routers/projects/projects.ts | grep -i "execWithShellEnv\|import"

echo ""
echo "=== Search for execWithShellEnv definition ==="
rg -B 3 -A 30 "execWithShellEnv" ./apps/desktop/src/lib/trpc/routers/projects/projects.ts | head -20

echo ""
echo "=== Find where execWithShellEnv might be defined ==="
find . -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "function execWithShellEnv\|const execWithShellEnv\|export.*execWithShellEnv" | head -10

Repository: superset-sh/superset

Length of output: 2115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Read shell-env.ts file ==="
wc -l ./apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts

echo ""
echo "=== Full execWithShellEnv implementation ==="
cat ./apps/desktop/src/lib/trpc/routers/workspaces/utils/shell-env.ts

Repository: superset-sh/superset

Length of output: 8705


Add timeout enforcement to the backend execWithShellEnv call.

The 10s timeout via Promise.race() in the renderer only bounds how long the renderer waits. The backend's gh issue view call via execWithShellEnv has no timeout, so slow GitHub API lookups will keep the process running in the main thread even after workspace creation continues. Node.js execFile supports a timeout option—pass it through execWithShellEnv to terminate the child process if it exceeds the timeout.

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

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx`
around lines 667 - 680, The renderer adds a 10s Promise.race timeout
(fetchWithTimeout) but the backend call using execWithShellEnv still runs gh
issue view with no child-process timeout; update the backend execWithShellEnv
implementation to accept an optional timeout parameter and pass it through to
Node's execFile/child_process invocation (use the execFile timeout option) so
the spawned gh process is killed if it exceeds the timeout, then update the
caller that runs the "gh issue view" command to supply the same 10_000ms timeout
when invoking execWithShellEnv.

Comment on lines +683 to +691
const issueContents = await Promise.all(
githubIssues.map(async (issue) => {
try {
const content = await fetchWithTimeout(
utils.client.projects.getIssueContent.query({
projectId,
issueNumber: issue.number,
}),
10000, // 10 second timeout per issue
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

Resolve GitHub issue attachments from the issue's repository, not projectId.

GitHub issue numbers are only unique within a repo. This lookup ignores the stored issue.url and re-resolves { projectId, issueNumber }, so a pasted cross-repo URL—or simply changing projects after linking—can attach the wrong issue body or 404. Persist a repo-scoped identifier (owner/repo or the full URL) and use that here.

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

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx`
around lines 683 - 691, The code re-resolves issues using { projectId,
issue.number } which can return wrong or 404 for cross-repo links; instead parse
and use the repo-scoped identifier from the stored issue (e.g. issue.url or
stored owner/repo) and call the repo-scoped API rather than
utils.client.projects.getIssueContent.query with projectId. Concretely: in the
githubIssues loop (githubIssues, issue.url, issue.number, fetchWithTimeout,
utils.client.projects.getIssueContent.query), extract owner and repo from
issue.url (or use the persisted owner/repo field), then call the repo-specific
endpoint (pass owner, repo, issueNumber) to fetch issue content; add a safe
fallback/log and timeout handling if parsing fails. Ensure you stop using
projectId for resolving cross-repo issue content.

Comment on lines +905 to +915
const issue = {
slug: `#${issueNumber}`,
title,
source: "github" as const,
url,
number: issueNumber,
state: normalizedState,
};
// Check for duplicates by URL to handle same issue numbers from different repos
if (linkedIssues.some((i) => i.url === url)) return;
updateDraft({ linkedIssues: [...linkedIssues, issue] });
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

Use a repo-scoped id instead of #${issueNumber} for GitHub issues.

The duplicate guard intentionally allows same-number issues from different repos via distinct URLs, but this slug collapses them back together. Line 1010 uses it as the React key, and Lines 918-920 remove by it, so two different repo-specific #123s will render and delete incorrectly. Use url or owner/repo#number as the stable identifier.

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

In
`@apps/desktop/src/renderer/components/NewWorkspaceModal/components/PromptGroup/PromptGroup.tsx`
around lines 905 - 915, The slug currently set to `#${issueNumber}` collapses
issues from different repos; change the slug on the created `issue` object to a
repo-scoped identifier (e.g. use `url` or `\`${owner}/${repo}#${issueNumber}\``)
instead of `#${issueNumber}` so it’s unique per repo, and update any logic that
relies on `issue.slug` (the linkedIssues list, the remove-by-id handler, and the
React key usage that references `issue.slug`) to use this new repo-scoped id so
rendering and deletion remain correct; ensure `updateDraft({ linkedIssues:
[...linkedIssues, issue] })` still pushes the new issue with the updated slug.

- Use TRPCError with proper error codes instead of plain Error
- Add 10s timeout to gh CLI calls to prevent hanging
- Trim whitespace from search query for URL matching
@AviPeltz AviPeltz merged commit 9964b15 into main Mar 21, 2026
6 of 7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

@Kitenite Kitenite deleted the plan-gh-issue-attachment branch March 25, 2026 00:39
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