feat(git): offer git init on create + auto-detect repo transitions#1027
Conversation
…r creation Two changes that remove the "restart Maestro" workaround when a working directory becomes a git repo after agent creation: 1. New `git:init` IPC handler (SSH-aware via the existing execGit helper), exposed through the preload bridge, global.d.ts typing, and a `gitService.init()` wrapper. Surfaced as an inline "Initialize as Git Repository" button in the wizard's "Regular Directory" panel and under the working-directory field in NewInstanceModal (with debounced isRepo detection so the offer only shows when the directory exists and isn't already a repo). 2. `useGitStatusPolling` now re-checks non-git sessions on each poll tick via a new `detectGitRepoTransitions()` helper. When a session's working dir flips to a real repo, `isGitRepo` is updated through `updateSessionWith` and branches/tags are cached, so worktree creation and other git-gated features unlock without an app restart. Tests updated: git handler registration count bumped to 27 and `'git:init'` added to the expected channel list. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughAdds end-to-end git init: a new ChangesGit Repository Initialization
sequenceDiagram
participant Renderer as Renderer
participant Preload as Preload
participant Main as MainProcess
participant GitExec as execGit
Renderer->>Preload: git.init(cwd, sshRemoteId?, remoteCwd?)
Preload->>Main: invoke 'git:init'
Main->>GitExec: execGit(['init'], effectiveCwd)
GitExec-->>Main: { exitCode, stderr }
Main-->>Preload: { success, error? }
Preload-->>Renderer: resolve { success, error? }
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
src/__tests__/main/ipc/handlers/git.test.ts (1)
168-199: ⚡ Quick winDerive handler-count assertion from
expectedChannelsto avoid drift.Use
expectedChannels.lengthinstead of duplicating27in the assertion.Suggested refactor
- it('should register all 27 git handlers', () => { + it('should register all git handlers', () => { const expectedChannels = [ 'git:status', 'git:diff', 'git:isRepo', 'git:init', @@ 'git:removeWorktree', 'git:createGist', ]; - expect(handlers.size).toBe(27); + expect(handlers.size).toBe(expectedChannels.length); for (const channel of expectedChannels) { expect(handlers.has(channel)).toBe(true); } });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/__tests__/main/ipc/handlers/git.test.ts` around lines 168 - 199, Replace the hard-coded numeric assertion with a derived one so it cannot drift: in the test where you build expectedChannels and assert handlers.size, change the assertion from expect(handlers.size).toBe(27) to expect(handlers.size).toBe(expectedChannels.length); locate the assertion near the expectedChannels array and update it to use expectedChannels.length (reference symbols: expectedChannels and handlers.size).src/renderer/hooks/git/useGitStatusPolling.ts (1)
180-209: ⚡ Quick winDon't blanket-swallow transition errors here.
gitService.isRepo/getBranches/getTagsalready absorb IPC failures and return defaults, so thiscatch {}mostly hides unexpected bugs in the transition path itself. If you need per-session isolation, usePromise.allSettledor narrow the catch to the specific recoverable step and report anything else.Based on learnings,
gitServicemethods implemented viacreateIpcMethodswallow IPC failures internally and already triggercaptureException.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/renderer/hooks/git/useGitStatusPolling.ts` around lines 180 - 209, The current per-session try/catch silently swallows all errors from the transition path (sessions.map block), hiding unexpected bugs; change to use Promise.allSettled over the sessions.map so each session is isolated and then for each settled result: onFulfilled apply the existing logic (calling gitService.isRepo/getBranches/getTags and updateSessionWith), and onRejected call the global error reporter (e.g., captureException or processLogger.error) with the thrown error and session.id so failures are visible; alternatively, if you prefer narrower handling, limit the try/catch to just the recoverable IPC-returning calls (gitService.isRepo/getBranches/getTags) and rethrow/log any other unexpected exceptions instead of swallowing them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/ipc/handlers/git.ts`:
- Around line 165-168: If sshRemoteId is passed but
getSshRemoteById(sshRemoteId) returns undefined, do not fall back to running
execGit locally; instead fail fast and return/throw an error. Update the handler
logic around sshRemoteId/sshRemote (where sshRemote is set using
getSshRemoteById) to detect that sshRemoteId was provided yet sshRemote is falsy
and respond with a clear error (e.g., reject the IPC request or return an error
result) before calling execGit(['init'], ...). Ensure effectiveRemoteCwd remains
computed only when a valid sshRemote exists.
In `@src/renderer/components/NewInstanceModal/NewInstanceModal.tsx`:
- Around line 316-322: Replace direct IPC calls to window.maestro.git in
NewInstanceModal (e.g., window.maestro.git.isRepo and any
window.maestro.git.init usages around the same block) with the renderer wrapper
methods on gitService (gitService.isRepo and gitService.init) so IPC errors are
handled and reported consistently; keep the existing cancelled check and
setGitRepoStatus('is-repo'|'not-repo'|'unknown') behavior but call gitService
methods instead of window.maestro.git, and remove any local try/catch that was
suppressing errors so the gitService error-handling/captureException behavior is
relied upon.
- Around line 296-329: The debounced git repo detection useEffect (the block
that reads workingDir, isSshEnabled, remotePathValidation.valid,
effectiveSshRemoteId and calls window.maestro.git.isRepo) should reset the
repo-init state immediately when starting a new check: call
setGitRepoStatus('unknown') and setInitRepoError(null) before scheduling the
setTimeout so the UI doesn't show 'not-repo' during the 500ms debounce; keep the
existing cancelled flag/timeout cleanup and the try/catch that sets the final
status after the async isRepo call.
In `@src/renderer/components/Wizard/screens/DirectorySelectionScreen.tsx`:
- Around line 364-381: Replace the direct preload IPC call in handleInitRepo
with the renderer-side gitService wrapper: instead of calling
window.maestro.git.init(state.directoryPath, sshRemoteId) use
gitService.init(state.directoryPath, sshRemoteId) so IPC failures are handled
centrally; keep the surrounding logic (checking result.success, setting
setInitRepoError, setIsGitRepo, setAnnouncement, and logger usage) the same but
remove the need to manually handle IPC fallbacks because gitService.init already
returns the fallback result and captures exceptions.
- Around line 774-805: The "Initialize as Git Repository" button can run against
a stale path because validation is debounced; update the UI to wait for the
latest validation before allowing initialization by gating the button on a
validation-in-flight flag. Add or reuse a boolean like isValidatingDirectory
(set true at the start of the async/debounced validateDirectory call and false
when it completes) and include it in the button's disabled prop alongside
isInitializingRepo (i.e. disabled={isInitializingRepo ||
isValidatingDirectory}), and ensure handleInitRepo reads state.directoryPath
only after validation completes; also prevent showing initRepoError until
validation finishes if relevant.
---
Nitpick comments:
In `@src/__tests__/main/ipc/handlers/git.test.ts`:
- Around line 168-199: Replace the hard-coded numeric assertion with a derived
one so it cannot drift: in the test where you build expectedChannels and assert
handlers.size, change the assertion from expect(handlers.size).toBe(27) to
expect(handlers.size).toBe(expectedChannels.length); locate the assertion near
the expectedChannels array and update it to use expectedChannels.length
(reference symbols: expectedChannels and handlers.size).
In `@src/renderer/hooks/git/useGitStatusPolling.ts`:
- Around line 180-209: The current per-session try/catch silently swallows all
errors from the transition path (sessions.map block), hiding unexpected bugs;
change to use Promise.allSettled over the sessions.map so each session is
isolated and then for each settled result: onFulfilled apply the existing logic
(calling gitService.isRepo/getBranches/getTags and updateSessionWith), and
onRejected call the global error reporter (e.g., captureException or
processLogger.error) with the thrown error and session.id so failures are
visible; alternatively, if you prefer narrower handling, limit the try/catch to
just the recoverable IPC-returning calls (gitService.isRepo/getBranches/getTags)
and rethrow/log any other unexpected exceptions instead of swallowing them.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 7ba1c9ec-e95e-4d02-b1af-297cb2c63033
📒 Files selected for processing (8)
src/__tests__/main/ipc/handlers/git.test.tssrc/main/ipc/handlers/git.tssrc/main/preload/git.tssrc/renderer/components/NewInstanceModal/NewInstanceModal.tsxsrc/renderer/components/Wizard/screens/DirectorySelectionScreen.tsxsrc/renderer/global.d.tssrc/renderer/hooks/git/useGitStatusPolling.tssrc/renderer/services/git.ts
Greptile SummaryThis PR adds a "Initialize as Git Repository" button to both the Onboarding Wizard and the New Agent modal, and adds auto-detection of
Confidence Score: 3/5The IPC layer and polling logic are solid, but both UI entry points have a state management gap that renders misleading error messages from a prior directory's failed git init on a new directory's offer panel. The main process handler, preload, service layer, and polling transition detection are all well-implemented. The two renderer components share the same flaw: initRepoError is not cleared when the user changes the working directory to a new non-empty path, so a previous failed-init error can surface for an unrelated directory. Both affected components are on the critical agent-creation paths, making the misleading state visible to users during a key workflow. src/renderer/components/NewInstanceModal/NewInstanceModal.tsx and src/renderer/components/Wizard/screens/DirectorySelectionScreen.tsx — both need state resets when the working directory changes. Important Files Changed
Sequence DiagramsequenceDiagram
participant UI as NewInstanceModal / Wizard
participant Renderer as Renderer (gitService)
participant IPC as IPC (git:init / git:isRepo)
participant Git as git binary (local/SSH)
UI->>Renderer: workingDir change (debounced 500ms)
Renderer->>IPC: git:isRepo(cwd, sshRemoteId)
IPC->>Git: git rev-parse --is-inside-work-tree
Git-->>IPC: exitCode 0/128
IPC-->>Renderer: true / false
Renderer-->>UI: "setGitRepoStatus('is-repo'|'not-repo')"
UI->>Renderer: handleInitRepo()
Renderer->>IPC: git:init(cwd, sshRemoteId)
IPC->>Git: git init
Git-->>IPC: exitCode 0 / stderr
IPC-->>Renderer: "{success, error?}"
Renderer-->>UI: setGitRepoStatus('is-repo') OR setInitRepoError(msg)
Note over Renderer: Poll tick (30-90s)
Renderer->>IPC: detectGitRepoTransitions(nonGitSessions)
IPC->>Git: git:isRepo per session
Git-->>IPC: true (transitioned)
IPC->>Git: git:branches + git:tags
Git-->>IPC: branches[], tags[]
IPC-->>Renderer: updateSessionWith(isGitRepo:true, branches, tags)
|
|
Thanks for the contribution, @chr1syy! 🎻 This is a nice quality-of-life improvement — the auto-detect of Both Greptile and CodeRabbit flagged a consistent set of issues, and a couple of them are worth addressing before we merge: 1. SSH remote must fail loudly ( 2. Stale 3. Debounce race on the init button — 4. (Nice-to-have) Route through Items 1–3 are the blockers; #4 is a cleanup we'd appreciate but won't hold the PR on. Once those are in, ping me for another pass. Thanks again! 🙏 |
- git.ts: reject IPC requests with an unresolvable sshRemoteId so we
never silently fall back to running `git init` on the wrong local
directory.
- NewInstanceModal: reset gitRepoStatus/initRepoError immediately when
the working dir changes so the stale "Initialize as Git Repository"
panel can't render (and run init) against an unvalidated path during
the 500ms debounce; route both isRepo and init through gitService for
centralized renderer-side error reporting.
- DirectorySelectionScreen: route init through gitService.init; flip
isValidating true at the start of the 800ms debounce in handlePathChange
so the (stale) git status panel is hidden during the debounce window,
and add isValidating to the Init button's disabled gate as defense in
depth.
- useGitStatusPolling: replace the blanket `try {} catch {}` with
Promise.allSettled so per-session failures are isolated; report
rejected promises to Sentry via captureException — gitService already
swallows IPC errors, so anything that reaches here is a real bug.
- git.test.ts: derive the handler-count assertion from
expectedChannels.length so it cannot drift when new handlers are added.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/__tests__/main/ipc/handlers/git.test.ts (1)
168-199: ⚡ Quick winAdd targeted
git:initbehavior tests, not just registration.This hunk verifies channel presence, but it doesn’t lock the new fail-fast contract (
sshRemoteIdunresolved must not execute localgit init). Please add focused handler tests for success and invalid-remote behavior.Suggested test additions
+ describe('git:init', () => { + it('should initialize a repository locally on success', async () => { + vi.mocked(execFile.execFileNoThrow).mockResolvedValue({ + stdout: 'Initialized empty Git repository', + stderr: '', + exitCode: 0, + }); + + const handler = handlers.get('git:init'); + const result = await handler!({} as any, '/test/repo'); + + expect(execFile.execFileNoThrow).toHaveBeenCalledWith('git', ['init'], '/test/repo'); + expect(result).toEqual({ success: true }); + }); + + it('should fail fast when sshRemoteId is provided but not found', async () => { + mockSettingsStore.get.mockReturnValue([]); + + const handler = handlers.get('git:init'); + const result = await handler!({} as any, '/test/repo', 'missing-remote'); + + expect(result).toEqual({ + success: false, + error: 'SSH remote not found: missing-remote', + }); + expect(execFile.execFileNoThrow).not.toHaveBeenCalled(); + }); + });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/__tests__/main/ipc/handlers/git.test.ts` around lines 168 - 199, Add focused tests for the 'git:init' handler (not just presence in handlers) to enforce the fail-fast contract: write one test that invokes the registered handler for 'git:init' with a valid sshRemoteId (or no sshRemoteId) and asserts it runs the local git init flow and returns success, and another test that invokes the handler with an unresolved/invalid sshRemoteId and asserts the local git init command is NOT executed and the handler returns the expected error/failure; locate the handler via the handlers Map used in the test suite (handlers.get('git:init')) and stub/mock the git execution utilities (or the function that performs init) to assert call/no-call and to simulate success/failure responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/__tests__/main/ipc/handlers/git.test.ts`:
- Around line 168-199: Add focused tests for the 'git:init' handler (not just
presence in handlers) to enforce the fail-fast contract: write one test that
invokes the registered handler for 'git:init' with a valid sshRemoteId (or no
sshRemoteId) and asserts it runs the local git init flow and returns success,
and another test that invokes the handler with an unresolved/invalid sshRemoteId
and asserts the local git init command is NOT executed and the handler returns
the expected error/failure; locate the handler via the handlers Map used in the
test suite (handlers.get('git:init')) and stub/mock the git execution utilities
(or the function that performs init) to assert call/no-call and to simulate
success/failure responses.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0d2abd1e-e1d9-4099-9b0a-f8d2bcfcfe84
📒 Files selected for processing (5)
src/__tests__/main/ipc/handlers/git.test.tssrc/main/ipc/handlers/git.tssrc/renderer/components/NewInstanceModal/NewInstanceModal.tsxsrc/renderer/components/Wizard/screens/DirectorySelectionScreen.tsxsrc/renderer/hooks/git/useGitStatusPolling.ts
Summary
git initthe working directory without leaving the create-agent flow.git initfrom terminal, cloning into the dir, etc.). TheuseGitStatusPollingloop now re-checks non-git sessions and flipsisGitRepoon transition, unlocking worktree creation and other git-gated features within one poll tick (~30–90s) — no restart required.git:initIPC handler routed through the existingexecGithelper, so the offer works for both local and SSH-remote working directories.Why
Today, if you create an agent on a non-git folder and later
git initit, Maestro keepssession.isGitRepo === falseuntil you restart the app, so "Create Worktree" never appears in the context menu or Quick Actions. This PR closes that gap and also surfaces an init offer at agent-create time so the user can opt in upfront.Changes
git:inithandler insrc/main/ipc/handlers/git.ts(SSH-aware).src/main/preload/git.ts,src/renderer/global.d.tsexposewindow.maestro.git.init().gitService.init()insrc/renderer/services/git.ts.src/renderer/hooks/git/useGitStatusPolling.ts— newdetectGitRepoTransitions()runs alongside the main poll for non-git sessions; on transition it callsupdateSessionWithand warmsgitBranches/gitTags.src/renderer/components/Wizard/screens/DirectorySelectionScreen.tsx— replaced the "you can initialize one later" copy with an inline button + error state.src/renderer/components/NewInstanceModal/NewInstanceModal.tsx— added debouncedgit.isRepodetection on the working-dir field and an offer panel when the directory isn't a repo. Gated onremotePathValidation.validfor SSH paths.src/__tests__/main/ipc/handlers/git.test.ts— bumped registered-handler count to 27 and added'git:init'to the expected channel list.Test plan
git initfrom a terminal in that folder → within ~30–90s the worktree action appears in the Quick Actions modal and session context menu without restarting Maestro.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Tests