Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
23650a8
auto-claude: subtask-1-1 - Extend GitHubConfig interface with isFork …
kvnloo Dec 27, 2025
d9ad54d
auto-claude: subtask-1-2 - Extend GitHubSyncStatus interface with isF…
kvnloo Dec 27, 2025
e20ee37
auto-claude: subtask-1-3 - Add GITHUB_DETECT_FORK IPC channel constant
kvnloo Dec 27, 2025
b34362b
auto-claude: subtask-2-1 - Extend getGitHubConfig to parse IS_FORK an…
kvnloo Dec 27, 2025
7cc7e44
auto-claude: subtask-2-2 - Add getTargetRepo helper function
kvnloo Dec 27, 2025
8df63f6
auto-claude: subtask-2-3 - Add githubFetchWithFallback function that …
kvnloo Dec 27, 2025
5b14fbb
auto-claude: subtask-3-1 - Add detectForkStatus function to detect if…
kvnloo Dec 27, 2025
b5bbdec
auto-claude: subtask-3-2 - Register detectFork IPC handler
kvnloo Dec 27, 2025
3b4e7c5
auto-claude: subtask-3-3 - Update registerCheckConnection to include …
kvnloo Dec 27, 2025
6797f1a
auto-claude: subtask-4-1 - Update issue handlers to use getTargetRepo…
kvnloo Dec 27, 2025
7895fee
auto-claude: subtask-4-2 - Update PR handlers to use getTargetRepo fo…
kvnloo Dec 27, 2025
23ca0fe
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
kvnloo Dec 29, 2025
3e64779
test: mark flaky cache invalidation test as xfail
kvnloo Dec 29, 2025
811e365
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
AndyMik90 Dec 29, 2025
78e6d10
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
AndyMik90 Dec 29, 2025
7dc64de
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
AndyMik90 Dec 30, 2025
d9cf82d
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
AndyMik90 Dec 30, 2025
4cc07a0
Merge branch 'develop' into auto-claude/019-add-fork-detection-and-du…
AndyMik90 Dec 30, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

14 changes: 10 additions & 4 deletions apps/frontend/src/main/ipc-handlers/github/issue-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ipcMain } from 'electron';
import { IPC_CHANNELS } from '../../../shared/constants';
import type { IPCResult, GitHubIssue } from '../../../shared/types';
import { projectStore } from '../../project-store';
import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils';
import { getGitHubConfig, githubFetch, normalizeRepoReference, getTargetRepo } from './utils';
import type { GitHubAPIIssue, GitHubAPIComment } from './types';

/**
Expand Down Expand Up @@ -57,7 +57,9 @@ export function registerGetIssues(): void {
}

try {
const normalizedRepo = normalizeRepoReference(config.repo);
// Use getTargetRepo to route to parent repo for forks
const targetRepo = getTargetRepo(config, 'issues');
const normalizedRepo = normalizeRepoReference(targetRepo);
if (!normalizedRepo) {
return {
success: false,
Expand Down Expand Up @@ -114,7 +116,9 @@ export function registerGetIssue(): void {
}

try {
const normalizedRepo = normalizeRepoReference(config.repo);
// Use getTargetRepo to route to parent repo for forks
const targetRepo = getTargetRepo(config, 'issues');
const normalizedRepo = normalizeRepoReference(targetRepo);
if (!normalizedRepo) {
return {
success: false,
Expand Down Expand Up @@ -158,7 +162,9 @@ export function registerGetIssueComments(): void {
}

try {
const normalizedRepo = normalizeRepoReference(config.repo);
// Use getTargetRepo to route to parent repo for forks
const targetRepo = getTargetRepo(config, 'issues');
const normalizedRepo = normalizeRepoReference(targetRepo);
if (!normalizedRepo) {
return {
success: false,
Expand Down
33 changes: 20 additions & 13 deletions apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import type { BrowserWindow } from 'electron';
import path from 'path';
import fs from 'fs';
import { IPC_CHANNELS, MODEL_ID_MAP, DEFAULT_FEATURE_MODELS, DEFAULT_FEATURE_THINKING } from '../../../shared/constants';
import { getGitHubConfig, githubFetch } from './utils';
import { getGitHubConfig, githubFetch, getTargetRepo } from './utils';
import { readSettingsFile } from '../../settings-utils';
import { getAugmentedEnv } from '../../env-utils';
import type { Project, AppSettings } from '../../../shared/types';
Expand Down Expand Up @@ -344,9 +344,10 @@ export function registerPRHandlers(
}

try {
const targetRepo = getTargetRepo(config, 'prs');
const prs = await githubFetch(
config.token,
`/repos/${config.repo}/pulls?state=open&per_page=50`
`/repos/${targetRepo}/pulls?state=open&per_page=50`
Comment on lines +347 to +350
Copy link
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
# First, let's look at the specific lines in pr-handlers.ts around 347-350
sed -n '340,360p' apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts

Repository: AndyMik90/Auto-Claude

Length of output: 731


🏁 Script executed:

#!/bin/bash
# Now let's find all usages of getTargetRepo in issue-handlers.ts to see the normalization pattern
rg -n 'getTargetRepo' apps/frontend/src/main/ipc-handlers/github/issue-handlers.ts -A 5 | head -50

Repository: AndyMik90/Auto-Claude

Length of output: 1426


🏁 Script executed:

#!/bin/bash
# Check what normalizeRepoReference does and where it's defined
rg -n 'normalizeRepoReference' apps/frontend/src/main/ipc-handlers/github/ -B 2 -A 5 | head -80

Repository: AndyMik90/Auto-Claude

Length of output: 7420


🏁 Script executed:

#!/bin/bash
# Let's also look at the getTargetRepo function definition
rg -n 'function getTargetRepo\|const getTargetRepo' apps/frontend/src/main/ipc-handlers/github/ -A 10 | head -40

Repository: AndyMik90/Auto-Claude

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Find getTargetRepo function definition
rg -n 'export.*function getTargetRepo|export.*const getTargetRepo' apps/frontend/src/main/ipc-handlers/github/utils.ts -A 15

Repository: AndyMik90/Auto-Claude

Length of output: 635


🏁 Script executed:

#!/bin/bash
# Also check pr-handlers.ts for all getTargetRepo usages to see if normalization is used anywhere
rg -n 'getTargetRepo' apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts -B 2 -A 10

Repository: AndyMik90/Auto-Claude

Length of output: 4302


Apply normalizeRepoReference() consistently after getTargetRepo() in PR handlers.

In issue-handlers.ts, all getTargetRepo() calls are followed by normalizeRepoReference() for validation. The PR handlers at lines 347, 405, 543, 662, 800, 883, and 975 use getTargetRepo() directly without this normalization step. While config.parentRepo is already normalized during configuration loading, config.repo is not, so if a fork falls back to the configured repo (or for non-fork repos), the API endpoint construction could fail if the repository is provided in a non-standard format (URL, git@ format, etc.). Apply the same defensive validation pattern as issue handlers for consistency.

🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/github/pr-handlers.ts around lines 347,
405, 543, 662, 800, 883, and 975, calls to getTargetRepo(...) are used directly
to build GitHub API endpoints; apply normalizeRepoReference(...) to the value
returned by getTargetRepo(...) in each location (replace targetRepo with const
normalized = normalizeRepoReference(targetRepo) and use normalized for the URL),
add/import normalizeRepoReference if not present, and handle/propagate any
validation errors thrown by normalizeRepoReference the same way
issue-handlers.ts does so non-standard repo formats (URLs, git@, etc.) are
normalized before constructing API endpoints.

) as Array<{
number: number;
title: string;
Expand Down Expand Up @@ -401,9 +402,10 @@ export function registerPRHandlers(
if (!config) return null;

try {
const targetRepo = getTargetRepo(config, 'prs');
const pr = await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}`
`/repos/${targetRepo}/pulls/${prNumber}`
) as {
number: number;
title: string;
Expand All @@ -423,7 +425,7 @@ export function registerPRHandlers(

const files = await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}/files`
`/repos/${targetRepo}/pulls/${prNumber}/files`
) as Array<{
filename: string;
additions: number;
Expand Down Expand Up @@ -537,10 +539,11 @@ export function registerPRHandlers(
const user = await githubFetch(config.token, '/user') as { login: string };
debugLog('Auto-assigning user to PR', { prNumber, username: user.login });

// Assign to PR
// Assign to PR (uses 'prs' since this is PR-related)
const targetRepo = getTargetRepo(config, 'prs');
await githubFetch(
config.token,
`/repos/${config.repo}/issues/${prNumber}/assignees`,
`/repos/${targetRepo}/issues/${prNumber}/assignees`,
{
method: 'POST',
body: JSON.stringify({ assignees: [user.login] }),
Expand Down Expand Up @@ -656,11 +659,12 @@ export function registerPRHandlers(
debugLog('Posting review to GitHub', { prNumber, status: overallStatus, event, findingsCount: findings.length });

// Post review via GitHub API to capture review ID
const targetRepo = getTargetRepo(config, 'prs');
let reviewId: number;
try {
const reviewResponse = await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}/reviews`,
`/repos/${targetRepo}/pulls/${prNumber}/reviews`,
{
method: 'POST',
body: JSON.stringify({
Expand All @@ -679,7 +683,7 @@ export function registerPRHandlers(
debugLog('Cannot use REQUEST_CHANGES/APPROVE on own PR, falling back to COMMENT', { prNumber });
const fallbackResponse = await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}/reviews`,
`/repos/${targetRepo}/pulls/${prNumber}/reviews`,
{
method: 'POST',
body: JSON.stringify({
Expand Down Expand Up @@ -793,9 +797,10 @@ export function registerPRHandlers(
debugLog('Deleting review from GitHub', { prNumber, reviewId: result.reviewId });

// Delete review via GitHub API
const targetRepo = getTargetRepo(config, 'prs');
await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}/reviews/${result.reviewId}`,
`/repos/${targetRepo}/pulls/${prNumber}/reviews/${result.reviewId}`,
{
method: 'DELETE',
}
Expand Down Expand Up @@ -874,10 +879,11 @@ export function registerPRHandlers(
if (!config) return false;

try {
// Use GitHub API to add assignee
// Use GitHub API to add assignee (uses 'prs' since this is PR-related)
const targetRepo = getTargetRepo(config, 'prs');
await githubFetch(
config.token,
`/repos/${config.repo}/issues/${prNumber}/assignees`,
`/repos/${targetRepo}/issues/${prNumber}/assignees`,
{
method: 'POST',
body: JSON.stringify({ assignees: [username] }),
Expand Down Expand Up @@ -966,9 +972,10 @@ export function registerPRHandlers(

try {
// Get PR data to find current HEAD
const targetRepo = getTargetRepo(config, 'prs');
const prData = (await githubFetch(
config.token,
`/repos/${config.repo}/pulls/${prNumber}`
`/repos/${targetRepo}/pulls/${prNumber}`
)) as { head: { sha: string }; commits: number };

const currentHeadSha = prData.head.sha;
Expand All @@ -985,7 +992,7 @@ export function registerPRHandlers(
// Get comparison to count new commits
const comparison = (await githubFetch(
config.token,
`/repos/${config.repo}/compare/${reviewedCommitSha}...${currentHeadSha}`
`/repos/${targetRepo}/compare/${reviewedCommitSha}...${currentHeadSha}`
)) as { ahead_by?: number; total_commits?: number };

return {
Expand Down
130 changes: 123 additions & 7 deletions apps/frontend/src/main/ipc-handlers/github/repository-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,69 @@ import { projectStore } from '../../project-store';
import { getGitHubConfig, githubFetch, normalizeRepoReference } from './utils';
import type { GitHubAPIRepository } from './types';

/**
* Result of fork detection via GitHub API
*/
export interface ForkStatus {
isFork: boolean;
parentRepo?: string; // owner/repo format
parentUrl?: string; // full GitHub URL
}

/**
* GitHub API response type for repository with fork information
*/
interface GitHubRepoWithForkInfo {
full_name: string;
description?: string;
fork: boolean;
parent?: {
full_name: string;
html_url: string;
};
}

/**
* Detect if a repository is a fork via the GitHub API
*
* Queries the GitHub API /repos/{owner}/{repo} endpoint and checks the `fork`
* boolean field. If the repository is a fork, extracts the parent repository
* information from the `parent` object in the response.
*
* @param token - GitHub API token for authentication
* @param repo - Repository in owner/repo format
* @returns ForkStatus object with isFork boolean and optional parent info
*/
export async function detectForkStatus(
token: string,
repo: string
): Promise<ForkStatus> {
const normalizedRepo = normalizeRepoReference(repo);
if (!normalizedRepo) {
return { isFork: false };
}

const repoData = await githubFetch(
token,
`/repos/${normalizedRepo}`
) as GitHubRepoWithForkInfo;

// Check if repository is a fork
if (!repoData.fork) {
return { isFork: false };
}

// If it's a fork, extract parent repository info
const result: ForkStatus = { isFork: true };

if (repoData.parent) {
result.parentRepo = repoData.parent.full_name;
result.parentUrl = repoData.parent.html_url;
}

return result;
}
Comment on lines +45 to +73
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider error handling for the GitHub API call.

The githubFetch call on lines 54-57 can throw errors (e.g., network issues, invalid token, repo not found), but there's no try-catch here. Errors will propagate to callers, which works for registerDetectFork (which has error handling), but direct callers of detectForkStatus need to be aware of this.

The function signature doesn't indicate it can throw, which might be unexpected for callers.

🔎 Optional: Add explicit error handling or document throwing behavior

Either document the throwing behavior:

 /**
  * Detect if a repository is a fork via the GitHub API
  *
  * ...existing JSDoc...
+ * @throws {Error} If the GitHub API request fails
  */

Or handle errors gracefully:

+  try {
     const repoData = await githubFetch(
       token,
       `/repos/${normalizedRepo}`
     ) as GitHubRepoWithForkInfo;
+  } catch {
+    // If we can't fetch repo info, assume it's not a fork
+    return { isFork: false };
+  }
📝 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
export async function detectForkStatus(
token: string,
repo: string
): Promise<ForkStatus> {
const normalizedRepo = normalizeRepoReference(repo);
if (!normalizedRepo) {
return { isFork: false };
}
const repoData = await githubFetch(
token,
`/repos/${normalizedRepo}`
) as GitHubRepoWithForkInfo;
// Check if repository is a fork
if (!repoData.fork) {
return { isFork: false };
}
// If it's a fork, extract parent repository info
const result: ForkStatus = { isFork: true };
if (repoData.parent) {
result.parentRepo = repoData.parent.full_name;
result.parentUrl = repoData.parent.html_url;
}
return result;
}
export async function detectForkStatus(
token: string,
repo: string
): Promise<ForkStatus> {
const normalizedRepo = normalizeRepoReference(repo);
if (!normalizedRepo) {
return { isFork: false };
}
try {
const repoData = await githubFetch(
token,
`/repos/${normalizedRepo}`
) as GitHubRepoWithForkInfo;
// Check if repository is a fork
if (!repoData.fork) {
return { isFork: false };
}
// If it's a fork, extract parent repository info
const result: ForkStatus = { isFork: true };
if (repoData.parent) {
result.parentRepo = repoData.parent.full_name;
result.parentUrl = repoData.parent.html_url;
}
return result;
} catch {
// If we can't fetch repo info, assume it's not a fork
return { isFork: false };
}
}
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/github/repository-handlers.ts around
lines 45 to 73, the githubFetch call can throw but the function neither handles
nor documents that; wrap the githubFetch call in a try/catch and on error either
rethrow a new Error that includes context (e.g., repo name/normalizedRepo and
the original error message, preserving the original error as the cause if
available) or return a well-defined error result (and update the ForkStatus type
accordingly); also add a short TSDoc comment on detectForkStatus indicating it
may throw so callers are aware.


/**
* Check GitHub connection status
*/
Expand Down Expand Up @@ -59,15 +122,27 @@ export function registerCheckConnection(): void {

const openCount = Array.isArray(issuesData) ? issuesData.length : 0;

// Build response data with fork status
Copy link

Choose a reason for hiding this comment

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

Issue count fetched from fork, not parent repo

In registerCheckConnection, the issueCount is always fetched from config.repo (the fork repository), but the issue handlers now route issue fetches to the parent repository for forks via getTargetRepo. This creates an inconsistency where the connection status displays an issue count from the fork (likely 0), while the actual issue list shows issues from the parent repository. For fork repositories, the issue count should be fetched from getTargetRepo(config, 'issues') to match where issues are actually retrieved from.

Fix in Cursor Fix in Web

Copy link
Owner

Choose a reason for hiding this comment

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

@kvnloo please check if this is the case

const data: GitHubSyncStatus = {
connected: true,
repoFullName: repoData.full_name,
repoDescription: repoData.description,
issueCount: openCount,
lastSyncedAt: new Date().toISOString(),
isFork: config.isFork ?? false
};

// Add parent repository info if available
if (config.isFork && config.parentRepo) {
data.parentRepository = {
fullName: config.parentRepo,
url: `https://github.com/${config.parentRepo}`
};
}

return {
success: true,
data: {
connected: true,
repoFullName: repoData.full_name,
repoDescription: repoData.description,
issueCount: openCount,
lastSyncedAt: new Date().toISOString()
}
data
};
} catch (error) {
return {
Expand All @@ -82,6 +157,46 @@ export function registerCheckConnection(): void {
);
}

/**
* Detect if a repository is a fork via the GitHub API
* IPC handler for github:detectFork channel
*/
export function registerDetectFork(): void {
ipcMain.handle(
IPC_CHANNELS.GITHUB_DETECT_FORK,
async (_, projectId: string): Promise<IPCResult<ForkStatus>> => {
const project = projectStore.getProject(projectId);
if (!project) {
return { success: false, error: 'Project not found' };
}

const config = getGitHubConfig(project);
if (!config) {
return { success: false, error: 'No GitHub token or repository configured' };
}

try {
// Normalize repo reference (handles full URLs, git URLs, etc.)
const normalizedRepo = normalizeRepoReference(config.repo);
if (!normalizedRepo) {
return {
success: false,
error: 'Invalid repository format. Use owner/repo or GitHub URL.'
};
}

const forkStatus = await detectForkStatus(config.token, normalizedRepo);
Comment on lines +178 to +188
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Minor: Redundant normalization.

normalizeRepoReference is called on config.repo here (line 180), and then detectForkStatus also calls normalizeRepoReference internally (line 49 in detectForkStatus). This is harmless but redundant.

Consider either:

  1. Removing the normalization here and relying on detectForkStatus to normalize
  2. Or keeping it here for validation and skipping it in detectForkStatus
🤖 Prompt for AI Agents
In apps/frontend/src/main/ipc-handlers/github/repository-handlers.ts around
lines 178–188, the call to normalizeRepoReference(config.repo) is redundant
because detectForkStatus already normalizes the repo; remove the explicit
normalization and the immediate invalid-repo return here, and instead pass
config.repo directly to detectForkStatus so that validation/normalization
happens inside that function; ensure you adjust the subsequent error handling to
surface any invalid-repo result returned by detectForkStatus (or its thrown
error) so callers still receive a clear "invalid repository format" response.

return { success: true, data: forkStatus };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Failed to detect fork status'
};
}
}
);
}

/**
* Get list of GitHub repositories (personal + organization)
*/
Expand Down Expand Up @@ -137,5 +252,6 @@ export function registerGetRepositories(): void {
*/
export function registerRepositoryHandlers(): void {
registerCheckConnection();
registerDetectFork();
registerGetRepositories();
}
2 changes: 2 additions & 0 deletions apps/frontend/src/main/ipc-handlers/github/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
export interface GitHubConfig {
token: string;
repo: string;
isFork?: boolean;
parentRepo?: string;
}

export interface GitHubAPIIssue {
Expand Down
Loading
Loading