Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
feat: add issue comment for empty repo in direct access path (#1230)
When an empty repository is detected in the direct access path (not fork
path), post a comment on the issue informing the user about the problem
and suggesting solutions, matching the existing behavior in the fork path.

- Add tryCommentOnIssueAboutEmptyRepo() helper in solve.repo-setup.lib.mjs
- Post comment when --auto-init-repository is not enabled (suggest the flag)
- Post comment when --auto-init-repository fails (suggest manual init)
- Skip comment when --auto-init-repository succeeds (no user action needed)
- Pass issueUrl to verifyDefaultBranchAndStatus() for comment posting
- Add 5 new tests (13-17) covering the comment behavior
- Update case study documentation

Fixes #1230

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
  • Loading branch information
konard and claude committed Feb 7, 2026
commit aef859f296b6720d8094d69dde17fa6d7919a586
11 changes: 7 additions & 4 deletions docs/case-studies/issue-1230/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,14 @@ Added a new CLI option `--auto-init-repository` that:
2. **Auto-initializes** using the existing `tryInitializeEmptyRepository()` function (creates README.md via GitHub API)
3. **Re-fetches and continues** - After initialization, fetches the new commit, checks out the default branch, and proceeds normally

### Improved Error Messages
### Improved Error Messages and Issue Comments

- When `--auto-init-repository` is NOT enabled: Clear message suggesting the flag
- When `--auto-init-repository` is NOT enabled: Clear message suggesting the flag + comment on the issue informing the user
- When `--auto-init-repository` fails: Clear error message + comment on the issue with actionable guidance
- When `--auto-init-repository` succeeds: No issue comment posted (no user action needed)
- When branch creation fails due to empty repo: Specific "is not a commit" pattern detection with actionable fix suggestion
- Reuses existing `tryInitializeEmptyRepository()` code (DRY principle)
- Issue comment behavior mirrors the existing fork-path comment in `solve.repository.lib.mjs`

## Files Changed

Expand All @@ -69,8 +72,8 @@ Added a new CLI option `--auto-init-repository` that:
| `src/solve.config.lib.mjs` | Added `--auto-init-repository` option definition |
| `src/option-suggestions.lib.mjs` | Added to `KNOWN_OPTION_NAMES` for typo detection |
| `src/solve.repository.lib.mjs` | Exported `tryInitializeEmptyRepository` for reuse |
| `src/solve.repo-setup.lib.mjs` | Added empty repo detection and auto-init in `verifyDefaultBranchAndStatus()` |
| `src/solve.mjs` | Pass `argv`, `owner`, `repo` to `verifyDefaultBranchAndStatus()` |
| `src/solve.repo-setup.lib.mjs` | Added empty repo detection, auto-init, and issue comment in `verifyDefaultBranchAndStatus()` |
| `src/solve.mjs` | Pass `argv`, `owner`, `repo`, `issueUrl` to `verifyDefaultBranchAndStatus()` |
| `src/solve.branch-errors.lib.mjs` | Improved error message for empty repo branch creation failures |

## Artifacts
Expand Down
13 changes: 11 additions & 2 deletions experiments/test-auto-init-repository.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ async function testAutoInitRepository() {

// Check solve.mjs passes new parameters
const solveContent = readFileSync(join(srcDir, 'solve.mjs'), 'utf-8');
if (solveContent.includes('argv,') && solveContent.includes('owner,') && solveContent.includes('repo,')) {
log(' ✅ solve.mjs passes argv, owner, repo to verifyDefaultBranchAndStatus');
if (solveContent.includes('argv,') && solveContent.includes('owner,') && solveContent.includes('repo,') && solveContent.includes('issueUrl,')) {
log(' ✅ solve.mjs passes argv, owner, repo, issueUrl to verifyDefaultBranchAndStatus');
} else {
log(' ❌ solve.mjs may not pass all required parameters');
}
Expand All @@ -98,6 +98,13 @@ async function testAutoInitRepository() {
log(' ❌ Branch error handler may be incomplete');
}

// Check issue comment functionality
if (repoSetupContent.includes('tryCommentOnIssueAboutEmptyRepo') && repoSetupContent.includes('gh issue comment')) {
log(' ✅ Issue comment functionality for empty repos implemented');
} else {
log(' ❌ Issue comment functionality may be incomplete');
}

log('\n=== Summary ===');
log('The --auto-init-repository feature:');
log('1. ✅ Adds new CLI option --auto-init-repository (default: false)');
Expand All @@ -107,6 +114,8 @@ async function testAutoInitRepository() {
log('5. ✅ Provides clear error messages when auto-init is disabled or fails');
log('6. ✅ Improves branch creation error messages for empty repo cases');
log('7. ✅ Maintains backward compatibility (default: false, opt-in only)');
log('8. ✅ Posts comment on issue when empty repo cannot be resolved (similar to fork path)');
log('9. ✅ Skips issue comment when auto-init succeeds (no user action needed)');
}

testAutoInitRepository().catch(console.error);
3 changes: 2 additions & 1 deletion src/solve.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ try {
});

// Verify default branch and status using the new module
// Pass argv, owner, repo for empty repository auto-initialization (--auto-init-repository)
// Pass argv, owner, repo, issueUrl for empty repository auto-initialization (--auto-init-repository)
const defaultBranch = await verifyDefaultBranchAndStatus({
tempDir,
log,
Expand All @@ -548,6 +548,7 @@ try {
argv,
owner,
repo,
issueUrl,
});
// Create or checkout branch using the new module
const branchName = await createOrCheckoutBranch({
Expand Down
59 changes: 58 additions & 1 deletion src/solve.repo-setup.lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ async function setupPrForkRemote(tempDir, argv, prForkOwner, repo, isContinueMod
return await setupPrForkFn(tempDir, argv, prForkOwner, repo, isContinueMode, owner);
}

export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned, $, argv, owner, repo }) {
export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned, $, argv, owner, repo, issueUrl }) {
// Verify we're on the default branch and get its name
const defaultBranchResult = await $({ cwd: tempDir })`git branch --show-current`;

Expand Down Expand Up @@ -139,6 +139,10 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
await log(' --method PUT --field message="Initialize repository" \\');
await log(' --field content="$(echo "# repo" | base64)"');
await log('');

// Post a comment on the issue informing about the empty repository
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });

throw new Error('Empty repository auto-initialization failed');
}
} else if (isEmptyRepo) {
Expand All @@ -157,6 +161,10 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
await log(' Option 2: Ask repository owner to add initial content');
await log(' Even a simple README.md file would allow branch creation');
await log('');

// Post a comment on the issue informing about the empty repository
await tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ });

throw new Error('Empty repository detected - use --auto-init-repository to initialize');
} else {
// Not an empty repo, some other issue with branch detection
Expand Down Expand Up @@ -200,6 +208,55 @@ export async function verifyDefaultBranchAndStatus({ tempDir, log, formatAligned
return defaultBranch;
}

/**
* Try to post a comment on the issue informing the user about the empty repository.
* This is a non-critical operation - errors are silently ignored.
* When --auto-init-repository succeeds, no comment is posted (no action needed from the user).
*/
async function tryCommentOnIssueAboutEmptyRepo({ issueUrl, owner, repo, log, formatAligned, $ }) {
if (!issueUrl) return;

try {
const issueMatch = issueUrl.match(/\/issues\/(\d+)/);
if (!issueMatch) return;

const issueNumber = issueMatch[1];
await log(`${formatAligned('💬', 'Creating comment:', 'Informing about empty repository...')}`);

const commentBody = `## ⚠️ Repository Initialization Required

Hello! I attempted to work on this issue, but encountered a problem:

**Issue**: The repository is empty (no commits) and branches cannot be created.
**Reason**: Git cannot create branches in a repository with no commits.

### 🔧 How to resolve:

**Option 1: Use \`--auto-init-repository\` flag**
Re-run the solver with the \`--auto-init-repository\` flag to automatically create a simple README.md:
\`\`\`
solve ${issueUrl} --auto-init-repository
\`\`\`

**Option 2: Initialize the repository yourself**
Please add initial content to the repository. Even a simple README.md (even if it is empty or contains just the title) file would make it possible to create branches and work on this issue.

Once the repository contains at least one commit with any file, I'll be able to proceed with solving this issue.

Thank you!`;

const commentResult = await $`gh issue comment ${issueNumber} --repo ${owner}/${repo} --body ${commentBody}`;
if (commentResult.code === 0) {
await log(`${formatAligned('✅', 'Comment created:', `Posted to issue #${issueNumber}`)}`);
} else {
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
}
} catch {
// Silently ignore comment creation errors - not critical to the process
await log(`${formatAligned('⚠️', 'Note:', 'Could not post comment to issue (this is not critical)')}`);
}
}

/**
* Detect if a cloned repository is empty (has no commits).
* An empty repository has no branches and no commits.
Expand Down
80 changes: 73 additions & 7 deletions tests/test-auto-init-repository.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@ runTest('tryInitializeEmptyRepository is exported from solve.repository.lib.mjs'
});

// =============================================
// Test 4: verifyDefaultBranchAndStatus accepts new parameters
// Test 4: verifyDefaultBranchAndStatus accepts new parameters including issueUrl
// =============================================
runTest('verifyDefaultBranchAndStatus accepts argv, owner, repo parameters', () => {
runTest('verifyDefaultBranchAndStatus accepts argv, owner, repo, issueUrl parameters', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
assert(repoSetupContent.includes('argv, owner, repo'), 'verifyDefaultBranchAndStatus should accept argv, owner, repo parameters');
assert(repoSetupContent.includes('argv, owner, repo, issueUrl'), 'verifyDefaultBranchAndStatus should accept argv, owner, repo, issueUrl parameters');
});

// =============================================
Expand Down Expand Up @@ -112,12 +112,12 @@ runTest('handleBranchCreationError detects "is not a commit" empty repo pattern'
});

// =============================================
// Test 9: solve.mjs passes required parameters
// Test 9: solve.mjs passes required parameters including issueUrl
// =============================================
runTest('solve.mjs passes argv, owner, repo to verifyDefaultBranchAndStatus', () => {
runTest('solve.mjs passes argv, owner, repo, issueUrl to verifyDefaultBranchAndStatus', () => {
const solveContent = readFileSync(join(srcDir, 'solve.mjs'), 'utf-8');
// Check that the call includes argv, owner, repo
assert(solveContent.includes('argv,\n owner,\n repo,'), 'solve.mjs should pass argv, owner, repo to verifyDefaultBranchAndStatus');
// Check that the call includes argv, owner, repo, issueUrl
assert(solveContent.includes('argv,\n owner,\n repo,\n issueUrl,'), 'solve.mjs should pass argv, owner, repo, issueUrl to verifyDefaultBranchAndStatus');
});

// =============================================
Expand Down Expand Up @@ -154,6 +154,72 @@ runTest('Case study documentation created for issue #1230', () => {
assert(logContent.includes('BRANCH CREATION FAILED'), 'Solve log should contain the original error');
});

// =============================================
// Test 13: tryCommentOnIssueAboutEmptyRepo helper function exists
// =============================================
runTest('tryCommentOnIssueAboutEmptyRepo helper function is implemented', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
assert(repoSetupContent.includes('async function tryCommentOnIssueAboutEmptyRepo'), 'tryCommentOnIssueAboutEmptyRepo helper should exist');
assert(repoSetupContent.includes('gh issue comment'), 'Should use gh issue comment to post to the issue');
assert(repoSetupContent.includes('Repository Initialization Required'), 'Comment body should explain the issue clearly');
assert(repoSetupContent.includes('--auto-init-repository'), 'Comment should suggest --auto-init-repository flag');
});

// =============================================
// Test 14: Comment is posted when empty repo detected without --auto-init-repository flag
// =============================================
runTest('Issue comment is posted when empty repo detected without --auto-init-repository', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
// Find the "else if (isEmptyRepo)" block and verify it calls tryCommentOnIssueAboutEmptyRepo
const emptyRepoBlock = repoSetupContent.indexOf('EMPTY REPOSITORY DETECTED');
const throwAfterBlock = repoSetupContent.indexOf("throw new Error('Empty repository detected - use --auto-init-repository to initialize');");
assert(emptyRepoBlock !== -1, 'Should have EMPTY REPOSITORY DETECTED block');
assert(throwAfterBlock !== -1, 'Should throw error after empty repo detection');
const blockContent = repoSetupContent.substring(emptyRepoBlock, throwAfterBlock);
assert(blockContent.includes('tryCommentOnIssueAboutEmptyRepo'), 'Should call tryCommentOnIssueAboutEmptyRepo before throwing');
});

// =============================================
// Test 15: Comment is posted when auto-init fails
// =============================================
runTest('Issue comment is posted when auto-init fails', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
// Find the "AUTO-INIT FAILED" block and verify it calls tryCommentOnIssueAboutEmptyRepo
const autoInitFailedBlock = repoSetupContent.indexOf('AUTO-INIT FAILED');
const throwAfterBlock = repoSetupContent.indexOf("throw new Error('Empty repository auto-initialization failed');");
assert(autoInitFailedBlock !== -1, 'Should have AUTO-INIT FAILED block');
assert(throwAfterBlock !== -1, 'Should throw error after auto-init failure');
const blockContent = repoSetupContent.substring(autoInitFailedBlock, throwAfterBlock);
assert(blockContent.includes('tryCommentOnIssueAboutEmptyRepo'), 'Should call tryCommentOnIssueAboutEmptyRepo before throwing');
});

// =============================================
// Test 16: No comment posted when auto-init succeeds
// =============================================
runTest('No issue comment is posted when auto-init succeeds', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
// Find the success path (between 'initialized' check and the 'else' for auto-init failure)
const successStart = repoSetupContent.indexOf("await log(`${formatAligned('✅', 'Repository initialized:'");
const successEnd = repoSetupContent.indexOf('AUTO-INIT FAILED');
assert(successStart !== -1, 'Should have Repository initialized success message');
assert(successEnd !== -1, 'Should have AUTO-INIT FAILED block');
const successBlock = repoSetupContent.substring(successStart, successEnd);
assert(!successBlock.includes('tryCommentOnIssueAboutEmptyRepo'), 'Success path should NOT call tryCommentOnIssueAboutEmptyRepo');
});

// =============================================
// Test 17: Comment function handles missing issueUrl gracefully
// =============================================
runTest('tryCommentOnIssueAboutEmptyRepo handles missing issueUrl gracefully', () => {
const repoSetupContent = readFileSync(join(srcDir, 'solve.repo-setup.lib.mjs'), 'utf-8');
// Find the function definition
const funcStart = repoSetupContent.indexOf('async function tryCommentOnIssueAboutEmptyRepo');
assert(funcStart !== -1, 'Function should exist');
const funcBlock = repoSetupContent.substring(funcStart, funcStart + 500);
assert(funcBlock.includes('if (!issueUrl) return'), 'Should return early if issueUrl is not provided');
assert(funcBlock.includes('issueUrl.match(/\\/issues\\/(\\d+)/)'), 'Should extract issue number from URL');
});

// =============================================
// Summary
// =============================================
Expand Down
Loading