Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
32 changes: 32 additions & 0 deletions JOURNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1710,3 +1710,35 @@ The `git-branch-manager.test.ts` test was failing on Windows CI due to:
- `npm run build` ✅
- `npm run lint` ✅
- `npm test` ✅ (311 tests passing)

## 2026-01-28 - Fix Post-Run Exit Delay

### Context
After `✔ 🎉 All 11 tasks completed successfully!` appeared, there was a long delay before terminal control returned to the user. Investigation revealed un-awaited async git checkpoint commits kept the Node.js event loop alive after the final success message printed.

### Root Cause
The `run` command subscribed to `LoopEngine`'s `iterationEnd` event and started `checkpointManager.createCheckpoint(...)` asynchronously without awaiting it. These git operations (which invoke hooks, LFS, signing, or push operations) could continue running after "All tasks completed" was printed, delaying `process.exit()`.

### Changes Made
1. **src/commands/run.ts**
- Added a `checkpointQueue: Promise<void>` to serialize iteration checkpoint commits
- Replaced fire-and-forget `.createCheckpoint(...).then(...)` with chaining onto the queue
- Added `await checkpointQueue` immediately after `await engine.start(activeTask)` to flush pending commits before printing final output

2. **README.md**
- Added new Troubleshooting section: "Long delay before returning to prompt"
- Explains git checkpoint work (hooks/signing/LFS) can add latency
- Lists mitigations: disable `autoCommit`, disable `autoPush`, inspect/disable slow hooks

### Impact
- CLI now promptly returns control to the user after final success message
- Checkpoint commits still run (ensuring safety) but don't block the exit path
- Provides users with mitigation options for slow git operations

### Files Modified
- `src/commands/run.ts`
- `README.md`

### Validation
- `npm run build` ✅
- `npm test` ✅ (311 tests passing)
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,14 @@ Check status: `ghcralph status`
View checkpoints: `ghcralph rollback --list`
Rollback if needed: `ghcralph rollback`

### Long delay before returning to prompt
`ghcralph run` creates git checkpoint commits (and may auto-push depending on config). If your repo has slow git hooks (e.g. Husky), commit signing, or Git LFS filters, the command may take extra time to finish.

Mitigations:
- Disable checkpoints: set `autoCommit: false` (or `GHCRALPH_AUTO_COMMIT=false`)
- Disable pushing: set `autoPush: false` / `pushStrategy: manual`
- Inspect/disable slow hooks in `.git/hooks/` (or Husky scripts) if appropriate

## Credits & Attribution

**GitHub Copilot Ralph** is an opinionated interpretation of the **Ralph Wiggum loop** approach, originally proposed by **[Geoffrey Huntley](https://ghuntley.com/)**.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ghcralph",
"version": "0.1.4",
"version": "0.1.5",
"description": "GitHub Copilot Ralph - A cross-platform CLI for running autonomous agentic coding loops using the Ralph Wiggum pattern with GitHub Copilot",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
28 changes: 21 additions & 7 deletions src/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,11 @@ See also:
// Setup event listeners
const events = engine.getEvents();

// Iteration checkpoint commits are intentionally started asynchronously so the loop can continue,
// but we still need to wait for them at task end so the CLI can exit immediately after printing
// the final success line.
let checkpointQueue: Promise<void> = Promise.resolve();

events.on('iterationStart', (iteration, state) => {
debug(
`Iteration ${iteration}/${maxIterations} - Tokens: ${state.tokensUsed.toLocaleString()}`
Expand All @@ -500,28 +505,33 @@ See also:
if (record.summary) {
console.log(` ${dim(record.summary)}`);
}

// Create checkpoint commit after successful iterations
if (record.success && checkpointManager.isAutoCommitEnabled()) {
// Build task context for commit message if we have plan info
const taskContext: TaskContext | undefined = totalTasksInPlan > 0
? { taskNumber: totalTasksProcessed, totalTasks: totalTasksInPlan }
: undefined;

checkpointManager
.createCheckpoint(record.iteration, record.summary ?? 'iteration complete', record.tokensUsed, taskContext)
.then((checkpoint) => {

checkpointQueue = checkpointQueue
.then(async () => {
const checkpoint = await checkpointManager.createCheckpoint(
record.iteration,
record.summary ?? 'iteration complete',
record.tokensUsed,
taskContext
);
if (checkpoint) {
debug(`Checkpoint created: ${checkpoint.commitHash.substring(0, 7)}`);
// Update progress with commit hash
state.lastCheckpoint = checkpoint.commitHash;
}
})
.catch(() => {
// Ignore checkpoint errors
// Ignore checkpoint errors (keep queue alive)
});
}

// Update in-memory progress state (file written at task completion)
progressTracker.setCurrentTask(totalTasksProcessed, state);
});
Expand All @@ -541,6 +551,10 @@ See also:
try {
const finalState = await engine.start(activeTask);

// Flush any queued iteration checkpoint commits before we stop spinners / print final output.
await checkpointQueue;
checkpointQueue = Promise.resolve();

loopSpinner.stop();
console.log('');

Expand Down