Skip to content

fix: use selected provider for git commit/PR/changelog generation#34

Open
zortos293 wants to merge 4 commits intomainfrom
capy/fix-provider-git-gen
Open

fix: use selected provider for git commit/PR/changelog generation#34
zortos293 wants to merge 4 commits intomainfrom
capy/fix-provider-git-gen

Conversation

@zortos293
Copy link
Owner

@zortos293 zortos293 commented Mar 15, 2026

This PR fixes issue #33 where auto-generated git commit messages and PR content always used Codex, regardless of the user's selected provider. It threads the selected provider from the UI through the git action pipeline to the text generation service, enabling Copilot to be used when selected.

  • Added provider?: ProviderKind to GitRunStackedActionInput in packages/contracts/src/git.ts.
  • Updated TextGeneration service interfaces to accept provider in apps/server/src/git/Services/TextGeneration.ts.
  • Modified GitManager in apps/server/src/git/Layers/GitManager.ts to forward provider to commit, feature branch, and PR generation steps.
  • Implemented runCopilotJson in apps/server/src/git/Layers/CodexTextGeneration.ts using the Copilot SDK with approveAll permissions and sendAndWait for structured output.
  • Added routing logic in text generation to switch between runCopilotJson and runCodexJson based on input.provider.
  • Plumbed activeProvider through UI components in apps/web/src/components/GitActionsControl.tsx, ChatView.tsx, and chat/ChatHeader.tsx.
  • Added gitRunStackedActionMutationOptions plumbing in apps/web/src/lib/gitReactQuery.ts.
  • Added regression tests in apps/server/src/git/Layers/GitManager.test.ts and apps/web/src/lib/gitReactQuery.test.ts.

Open in Capy TC-16 · 5.4

Summary by CodeRabbit

  • New Features

    • Added GitHub Copilot as an alternative AI provider for generating commit messages, PR content, and branch names; Copilot runs alongside existing provider flows.
    • Enabled provider selection and propagation so the chosen AI provider is used consistently across git actions and UI.
  • Tests

    • Added tests to verify provider forwarding through git action flows.

@zortos293 zortos293 added the capy Generated by capy.ai label Mar 15, 2026 — with Capy AI
@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

📝 Walkthrough

Walkthrough

Adds optional provider-aware text generation to support both Codex and GitHub Copilot. Backend: introduces Copilot-specific constants, error normalization, JSON extraction helpers, and a parallel Copilot execution flow wired into existing commit/PR/branch generation paths. Services and GitManager thread an optional provider through generation calls. Frontend: passes activeProvider into ChatHeader and GitActionsControl, persists provider with pending actions, and includes it in mutation payloads and React Query types. Contracts: stacked-action RPC input gains an optional provider field.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 clearly summarizes the main change: threading the selected provider through the git action pipeline for commit/PR/changelog generation.
Description check ✅ Passed The description covers the problem (issue #33), explains why the change is needed, and provides a comprehensive list of all modified files with implementation details.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/server/src/git/Layers/CodexTextGeneration.ts (1)

623-625: ⚠️ Potential issue | 🟠 Major

Copilot branch-name generation silently drops the actual image inputs.

materializeImageAttachments() still resolves image paths before provider selection, but only the Codex branch passes them downstream. When input.provider === "copilot", the prompt still says images are primary context even though the model only gets attachment metadata, so screenshot-driven branch naming will regress on the new provider. Either send the images to Copilot too, or fail/fallback when attachments are present.

As per coding guidelines, apps/server/**: Prioritize correctness, reliability, and predictable behavior under reconnects, session restarts, partial streams, and provider failures.

Also applies to: 657-671

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

In `@apps/server/src/git/Layers/CodexTextGeneration.ts` around lines 623 - 625,
When generating branch names, the call to
materializeImageAttachments("generateBranchName", input.attachments) resolves
and returns imagePaths but those image files are only passed into the Codex
path; when input.provider === "copilot" the prompt still references images even
though Copilot only receives attachment metadata, causing silent loss of image
context. Fix by either sending the actual imagePaths to the Copilot provider
path or, if Copilot cannot accept images, reject/fallback when attachments
exist: update the generateBranchName flow to check input.provider and imagePaths
and (a) include imagePaths in the Copilot request if the Copilot client supports
binary/image content, or (b) throw/return a deterministic fallback (e.g., fail
with a clear error or route to Codex) when attachments.length > 0 and
input.provider === "copilot"; adjust code around materializeImageAttachments,
the branch that handles input.provider, and any prompt construction to ensure
prompts only claim image context when the chosen provider actually receives the
images.
🧹 Nitpick comments (2)
apps/server/src/git/Layers/GitManager.ts (1)

637-637: Prefer a single provider type alias to avoid literal-union drift.

The repeated "codex" | "copilot" | undefined signatures are easy to desync over time. Consider deriving one local alias from GitManagerShape["runStackedAction"] and reusing it.

♻️ Suggested refactor
+type GitStackedActionProvider = Parameters<GitManagerShape["runStackedAction"]>[0]["provider"];
+
   const resolveCommitAndBranchSuggestion = (input: {
     cwd: string;
-    provider?: "codex" | "copilot";
+    provider?: GitStackedActionProvider;
     branch: string | null;
     commitMessage?: string;
@@
   const runCommitStep = (
     cwd: string,
-    provider: "codex" | "copilot" | undefined,
+    provider: GitStackedActionProvider,
     branch: string | null,
@@
   const runPrStep = (
     cwd: string,
-    provider: "codex" | "copilot" | undefined,
+    provider: GitStackedActionProvider,
     fallbackBranch: string | null,
   ) =>
@@
   const runFeatureBranchStep = (
     cwd: string,
-    provider: "codex" | "copilot" | undefined,
+    provider: GitStackedActionProvider,
     branch: string | null,

Also applies to: 683-683, 713-713, 981-981

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

In `@apps/server/src/git/Layers/GitManager.ts` at line 637, Multiple places
declare provider?: "codex" | "copilot" causing drift; create a single local type
alias (e.g., type Provider = GitManagerShape["runStackedAction"] extends (args:
infer A) => any ? A extends { provider?: infer P } ? P : never : never) and
replace all explicit `"codex" | "copilot"` literals with that alias; update the
provider properties/signatures for the provider?: fields and parameters used
across GitManager (e.g., the provider property on relevant interfaces/objects
and the parameters of runStackedAction and any callers) so they all reference
the new Provider type alias.
apps/server/src/git/Layers/GitManager.test.ts (1)

850-902: Add coverage for the feature-branch provider path too.

This regression proves provider propagation into generateCommitMessage and generatePrContent, but generateBranchName is never exercised. A future drop in the feature-branch path would still pass here, so please add a case that runs with featureBranch: true and asserts the selected provider reaches branch-name generation as well.

As per coding guidelines, apps/server/**: Prioritize correctness, reliability, and predictable behavior under reconnects, session restarts, partial streams, and provider failures.

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

In `@apps/server/src/git/Layers/GitManager.test.ts` around lines 850 - 902, The
test currently checks provider propagation into generateCommitMessage and
generatePrContent but never exercises generateBranchName; update the test that
uses makeManager and runStackedAction to include featureBranch: true and wire
generateBranchName into the textGeneration stub (push the seen provider into
seenProviders inside generateBranchName) and assert that seenProviders becomes
["copilot","copilot","copilot"] (or includes the extra "copilot" entry) after
runStackedAction; reference the existing symbols generateBranchName,
generateCommitMessage, generatePrContent, makeManager, runStackedAction and the
seenProviders array when making the change.
🤖 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/server/src/git/Layers/CodexTextGeneration.ts`:
- Around line 470-477: The code currently calls client.createSession(...) with
onPermissionRequest: approveAll which unconditionally grants Copilot permission
requests; replace this by implementing mode-aware permission gating similar to
CopilotAdapter: detect the runtime mode/privilege (e.g., full-access vs
read-only/ephemeral) and either (a) suspend and forward permission requests for
explicit user approval in non-full-access modes or (b) reject/deny requests
outright when the session is sandboxed/read-only, instead of using approveAll;
update the createSession call (the client.start()/client.createSession(...)
block and COPILOT_MODEL usage) to pass a permission handler that checks the
current mode and calls the appropriate approve/deny/suspend logic consistent
with CopilotAdapter.

---

Outside diff comments:
In `@apps/server/src/git/Layers/CodexTextGeneration.ts`:
- Around line 623-625: When generating branch names, the call to
materializeImageAttachments("generateBranchName", input.attachments) resolves
and returns imagePaths but those image files are only passed into the Codex
path; when input.provider === "copilot" the prompt still references images even
though Copilot only receives attachment metadata, causing silent loss of image
context. Fix by either sending the actual imagePaths to the Copilot provider
path or, if Copilot cannot accept images, reject/fallback when attachments
exist: update the generateBranchName flow to check input.provider and imagePaths
and (a) include imagePaths in the Copilot request if the Copilot client supports
binary/image content, or (b) throw/return a deterministic fallback (e.g., fail
with a clear error or route to Codex) when attachments.length > 0 and
input.provider === "copilot"; adjust code around materializeImageAttachments,
the branch that handles input.provider, and any prompt construction to ensure
prompts only claim image context when the chosen provider actually receives the
images.

---

Nitpick comments:
In `@apps/server/src/git/Layers/GitManager.test.ts`:
- Around line 850-902: The test currently checks provider propagation into
generateCommitMessage and generatePrContent but never exercises
generateBranchName; update the test that uses makeManager and runStackedAction
to include featureBranch: true and wire generateBranchName into the
textGeneration stub (push the seen provider into seenProviders inside
generateBranchName) and assert that seenProviders becomes
["copilot","copilot","copilot"] (or includes the extra "copilot" entry) after
runStackedAction; reference the existing symbols generateBranchName,
generateCommitMessage, generatePrContent, makeManager, runStackedAction and the
seenProviders array when making the change.

In `@apps/server/src/git/Layers/GitManager.ts`:
- Line 637: Multiple places declare provider?: "codex" | "copilot" causing
drift; create a single local type alias (e.g., type Provider =
GitManagerShape["runStackedAction"] extends (args: infer A) => any ? A extends {
provider?: infer P } ? P : never : never) and replace all explicit `"codex" |
"copilot"` literals with that alias; update the provider properties/signatures
for the provider?: fields and parameters used across GitManager (e.g., the
provider property on relevant interfaces/objects and the parameters of
runStackedAction and any callers) so they all reference the new Provider type
alias.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b656944f-115e-46f7-bdc9-812bdce72935

📥 Commits

Reviewing files that changed from the base of the PR and between 32de82c and 69ca7ab.

📒 Files selected for processing (10)
  • apps/server/src/git/Layers/CodexTextGeneration.ts
  • apps/server/src/git/Layers/GitManager.test.ts
  • apps/server/src/git/Layers/GitManager.ts
  • apps/server/src/git/Services/TextGeneration.ts
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/GitActionsControl.tsx
  • apps/web/src/components/chat/ChatHeader.tsx
  • apps/web/src/lib/gitReactQuery.test.ts
  • apps/web/src/lib/gitReactQuery.ts
  • packages/contracts/src/git.ts

Copy link

@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: 2

🤖 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/server/src/git/Layers/CodexTextGeneration.ts`:
- Around line 663-677: The Copilot branch-name path currently calls
runCopilotJson without imagePaths, dropping screenshot context; update the logic
in the generateBranchName branch to either pass imagePaths into runCopilotJson
(add imagePaths to the argument object) so Copilot receives attachments, or, if
Copilot cannot accept images yet, detect when imagePaths.length > 0 and fail
fast or explicitly fall back to runCodexJson (or return an error) so you don't
silently degrade output; modify the branch that checks input.provider and the
call to runCopilotJson (and related prompt/outputSchemaJson handling)
accordingly to surface attachments or fail/route predictably.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bb389e1f-c883-49e2-94ca-0b4aae4ca4bc

📥 Commits

Reviewing files that changed from the base of the PR and between 69ca7ab and f3c5cc7.

📒 Files selected for processing (1)
  • apps/server/src/git/Layers/CodexTextGeneration.ts

Co-authored-by: Capy <capy@capy.ai>
Copy link

@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: 2

🤖 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/server/src/git/Layers/CodexTextGeneration.ts`:
- Around line 171-191: withPromiseTimeout currently only rejects the wrapper
promise on timeout and doesn't stop the underlying Copilot work; update it so
the timeout path explicitly cancels the in-flight operation (e.g., invoke the
Copilot cancellation/stop API or abort callback) before rejecting. Specifically,
change withPromiseTimeout to call a cancellation function (provided via the
existing onTimeout or an additional parameter) that invokes
client.start()/createSession()/sendAndWait() cleanup (for example calling
session.cancel() or client.stop() or triggering an AbortSignal) and wait for
that cancellation to be initiated (but not indefinitely) before calling
reject(onTimeout()); apply the same change to the other instance referenced
around lines 498-530 so every timeout both rejects and actively interrupts the
in-flight Copilot session.
- Around line 510-523: Wrap the call to session.sendAndWait(...) in a try-catch
inside the function/method where COPILOT_TIMEOUT_MS is used; on success proceed
as before, but in the catch block call session.getMessages().catch(() => [] as
SessionEvent[]) and run extractLastCopilotAssistantText(history) to try to
recover assistantText from the session history, returning it if non-empty, and
only rethrow the original error (or throw a new error) if history-based recovery
yields no assistant content; ensure you preserve the existing logic that trims
content and checks assistantText.length === 0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 407cd84e-e75b-48fc-a05a-613857b90def

📥 Commits

Reviewing files that changed from the base of the PR and between f3c5cc7 and a13b857.

📒 Files selected for processing (1)
  • apps/server/src/git/Layers/CodexTextGeneration.ts

Co-authored-by: Capy <capy@capy.ai>
@github-actions github-actions bot added size:XL and removed size:L labels Mar 15, 2026
Copy link

@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.

♻️ Duplicate comments (1)
apps/server/src/git/Layers/CodexTextGeneration.ts (1)

576-579: ⚠️ Potential issue | 🟠 Major

Bound teardown awaits in finally to preserve timeout guarantees.

On timeout/failure paths, Line 577 and Line 578 still await cleanup without a deadline. If Copilot teardown hangs, request completion can exceed COPILOT_TIMEOUT_MS.

⏱️ Suggested fix
         } finally {
-          await session?.destroy().catch(() => undefined);
-          await client.stop().catch(() => []);
+          await Promise.race([
+            (session?.destroy().catch(() => undefined) ?? Promise.resolve(undefined)),
+            new Promise<void>((resolve) =>
+              setTimeout(resolve, COPILOT_TIMEOUT_CANCELLATION_GRACE_MS),
+            ),
+          ]);
+          await Promise.race([
+            client.stop().catch(() => undefined),
+            new Promise<void>((resolve) =>
+              setTimeout(resolve, COPILOT_TIMEOUT_CANCELLATION_GRACE_MS),
+            ),
+          ]);
         }

As per coding guidelines, "Prioritize correctness, reliability, and predictable behavior under reconnects, session restarts, partial streams, and provider failures."

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

In `@apps/server/src/git/Layers/CodexTextGeneration.ts` around lines 576 - 579,
The finally block currently awaits session?.destroy() and client.stop() with no
deadline, which can let a hanging teardown exceed COPILOT_TIMEOUT_MS; change the
teardown to use bounded awaits (e.g., Promise.race with a timeout based on
COPILOT_TIMEOUT_MS or an AbortController) so both session?.destroy() and
client.stop() are given a short, shared deadline and then ignored/caught if they
time out; ensure you still call both teardown functions (session?.destroy and
client.stop), catch and swallow/ log errors, and do not block the request beyond
the configured COPILOT_TIMEOUT_MS.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@apps/server/src/git/Layers/CodexTextGeneration.ts`:
- Around line 576-579: The finally block currently awaits session?.destroy() and
client.stop() with no deadline, which can let a hanging teardown exceed
COPILOT_TIMEOUT_MS; change the teardown to use bounded awaits (e.g.,
Promise.race with a timeout based on COPILOT_TIMEOUT_MS or an AbortController)
so both session?.destroy() and client.stop() are given a short, shared deadline
and then ignored/caught if they time out; ensure you still call both teardown
functions (session?.destroy and client.stop), catch and swallow/ log errors, and
do not block the request beyond the configured COPILOT_TIMEOUT_MS.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 441f6430-aca9-4c8b-a7d4-0eb743b692bd

📥 Commits

Reviewing files that changed from the base of the PR and between a13b857 and f2f2136.

📒 Files selected for processing (1)
  • apps/server/src/git/Layers/CodexTextGeneration.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

capy Generated by capy.ai size:XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant