Skip to content

feat: Github Setting for 'draft' mode#762

Open
jehiah wants to merge 7 commits into
ColeMurray:mainfrom
jehiah:github_draft_mode_762
Open

feat: Github Setting for 'draft' mode#762
jehiah wants to merge 7 commits into
ColeMurray:mainfrom
jehiah:github_draft_mode_762

Conversation

@jehiah

@jehiah jehiah commented Jun 18, 2026

Copy link
Copy Markdown

I want to add support for 'draft' mode PRs. This is important for me because it allows iterating on a PR before review is requested from required reviewers based on github CODEOWNERS files.

Summary

Lets coding sessions open pull requests in draft mode, controlled two ways:

  1. A new optional draft parameter on the agent's create-pull-request tool.
  2. A new "GitHub" integration setting — "Always use draft mode" — with a global default and
    per-repo overrides. This is intentionally separate from the existing "GitHub Bot" settings
    (PR reviews / comment actions); it lives in its own integration card.

Draft creation already existed at the source-control provider layer (github-provider.ts honors
config.draft; the GitLab provider uses the Draft: title prefix). This change threads the flag
through the tool call and adds the setting that supplies the default.

Resolution order: an explicit draft value from the tool call always wins; when omitted, the
resolved "always use draft mode" setting for the session's repo decides (global default merged with
per-repo override); when D1 is unavailable it falls back to false.

Screenshots

Changes

New draft tool parameter

  • packages/sandbox-runtime/.../plugins/inspect-plugin.jscreate-pull-request accepts an
    optional draft boolean and forwards it.
  • packages/control-plane/src/routes/session-runtime-proxy.ts — validates draft is a boolean and
    forwards it to the session Durable Object.
  • packages/control-plane/src/session/http/handlers/pull-request.handler.tsCreatePrRequest
    carries draft through to the service.
  • packages/control-plane/src/session/pull-request-service.ts — computes the effective draft value
    (input.draft ?? resolveAlwaysDraftDefault()) and passes it to the provider.

New "GitHub" integration setting (separate from "GitHub Bot")

  • packages/shared/src/types/integrations.ts — new IntegrationId "github-pr",
    GitHubPrSettings { alwaysUseDraftMode? }, settings-map entry, GitHubPrGlobalConfig, and a new
    INTEGRATION_DEFINITIONS card named "GitHub". The existing "GitHub Bot" card is unchanged.
  • packages/control-plane/src/db/integration-settings.ts — validates alwaysUseDraftMode is a
    boolean. Global-default + per-repo-override merge is inherited from the existing framework.
  • packages/control-plane/src/routes/integration-settings.ts — resolved-config branch for
    github-pr.
  • packages/control-plane/src/session/durable-object.tsresolveAlwaysDraftDefault() reads the
    resolved github-pr config for the session's repo; returns false when D1 is unavailable.
  • packages/web/src/components/settings/integrations/github-pr-integration-settings.tsx (new) +
    packages/web/src/app/(app)/settings/integrations/[id]/page.tsx — settings card with a global
    "Always use draft mode" toggle and per-repo overrides, mirroring the code-server pattern.

Scope notes

  • The setting applies to all session-created PRs, including sessions triggered by the GitHub
    bot's @mentions — this is the intended scope, and the setting remains fully decoupled from the
    GitHub Bot configuration.
  • No schema migration required — the generic integration_settings /
    integration_repo_settings tables already store arbitrary JSON keyed by integration id.

Testing

  • PR service (pull-request-service.test.ts): default draft=false, explicit flag forwarded,
    setting fallback when unspecified, and explicit draft=false overriding an enabled setting.
  • Settings store (integration-settings.test.ts): round-trip global/repo, non-boolean
    rejection, and repo override flipping the global default.
  • Proxy (session-runtime-proxy.test.ts): draft forwarding and non-boolean rejection.
  • Full control-plane unit suite (1353) and web suite (296) pass; typecheck and lint are clean on
    all changed files.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added draft pull request support across the create-PR flow (including optional draft input from the app/tooling)
    • Introduced an SCM Settings page for managing source-control defaults
    • Added an “Always use draft mode” toggle to default PRs to draft
    • Added per-repository SCM overrides to customize draft defaults by repository
  • Bug Fixes
    • Added validation to reject non-boolean draft values with a clear 400 error response

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an optional draft boolean to PR creation across the full stack—from the sandbox create-pull-request tool through the session runtime proxy, PR service, and GitHub provider—and introduces a new scm settings integration storing a global alwaysUseDraftMode flag with per-repository overrides, backed by ScmSettingsStore in D1 and exposed via new control-plane routes and a web settings UI.

Changes

SCM Draft Mode Feature

Layer / File(s) Summary
Shared types: ScmSettings and IntegrationSettingsMap extension
packages/shared/src/types/integrations.ts
Adds ScmSettings interface with alwaysUseDraftMode, registers "scm" in IntegrationSettingsMap, and exports ScmGlobalConfig and SlackGlobalConfig derived types.
IntegrationSettingsStore: relax generics to keyof IntegrationSettingsMap
packages/control-plane/src/db/integration-settings.ts
All public methods and internal helpers change generic constraint from IntegrationId to keyof IntegrationSettingsMap, enabling the "scm" key to be used with the same type-safe store.
ScmSettingsStore: D1 wrapper with validation and tests
packages/control-plane/src/db/scm-settings.ts, packages/control-plane/src/db/scm-settings.test.ts
Introduces ScmSettingsValidationError and ScmSettingsStore wrapping IntegrationSettingsStore for global/repo SCM config read/write/delete/list and resolved-settings merge. Tests verify round-trips, repo-key lowercasing, boolean validation of alwaysUseDraftMode, and fallback behavior.
Control-plane SCM settings routes and router wiring
packages/control-plane/src/routes/scm-settings.ts, packages/control-plane/src/router.ts
Defines and registers scmSettingsRoutes with GET/PUT/DELETE for global settings and list/PUT/DELETE for per-repo settings, mapping ScmSettingsValidationError to 400 and storage failure to 503.
PR service: draft field, resolveAlwaysDraftDefault, and integration
packages/control-plane/src/session/pull-request-service.ts, packages/control-plane/src/session/http/handlers/pull-request.handler.ts
CreatePullRequestInput gains optional draft; PullRequestServiceDeps adds resolveAlwaysDraftDefault(); createPullRequest computes draft as alwaysDraft || (input.draft ?? false) and forwards it to the provider.
SessionDO: wiring of resolveAlwaysDraftDefault from D1
packages/control-plane/src/session/durable-object.ts
createPullRequestHandler receives a resolveAlwaysDraftDefault callback; the new private method reads resolved SCM settings from ScmSettingsStore for the session repo, returning false on missing DB, failed session load, or lookup error.
Session runtime proxy: draft validation and forwarding
packages/control-plane/src/routes/session-runtime-proxy.ts, packages/control-plane/src/routes/session-runtime-proxy.test.ts
handleCreatePR validates draft is a boolean when present (400 on failure) and includes it in the forwarded POST body; tests cover valid forwarding and invalid-type rejection.
Sandbox inspect-plugin: draft arg on create-pull-request
packages/sandbox-runtime/src/sandbox_runtime/plugins/inspect-plugin.js
Adds optional draft boolean to the tool's Zod schema and includes it in the POST body sent to the control-plane PR endpoint.
PR service and GitHub provider tests for draft behavior
packages/control-plane/src/session/pull-request-service.test.ts, packages/control-plane/src/source-control/providers/github-provider.test.ts
PR service tests verify default draft: false, explicit forwarding, alwaysDraftDefault fallback, and override precedence. GitHub provider tests cover default PR creation, draft flag forwarding, and error propagation with httpStatus.
Next.js API proxy routes for SCM settings
packages/web/src/app/api/scm-settings/route.ts, packages/web/src/app/api/scm-settings/repos/route.ts, packages/web/src/app/api/scm-settings/repos/[owner]/[name]/route.ts
Adds session-authenticated Next.js API handlers at /api/scm-settings (GET/PUT/DELETE) and /api/scm-settings/repos (GET) and /api/scm-settings/repos/[owner]/[name] (PUT/DELETE), all proxying to controlPlaneFetch with upstream status pass-through.
Web settings UI: ScmSettingsPage, nav, and routing
packages/web/src/components/settings/scm-settings.tsx, packages/web/src/components/settings/settings-nav.tsx, packages/web/src/app/(app)/settings/page.tsx
ScmSettingsPage fetches global and repo override settings via SWR, renders a global draft-mode toggle with Save/Reset, and per-repo override rows with add/remove/save. Settings nav adds a "scm" entry with GitPrIcon; the settings page routes the "scm" tab to ScmSettingsPage.

Sequence Diagram

sequenceDiagram
  participant SandboxTool as inspect-plugin<br/>(create-pull-request)
  participant Proxy as session-runtime-proxy<br/>(handleCreatePR)
  participant Handler as pull-request.handler
  participant Service as SessionPullRequestService
  participant DOResolver as SessionDO<br/>.resolveAlwaysDraftDefault
  participant ScmStore as ScmSettingsStore<br/>(D1)
  participant GitHub as GitHub Provider

  SandboxTool->>Proxy: POST /sessions/:id/pr<br/>{ title, body, draft? }
  Proxy->>Proxy: validate draft is boolean if present
  Proxy->>Handler: POST SessionInternalPaths.createPr<br/>{ ..., draft }
  Handler->>Service: createPullRequest({ ..., draft })
  Service->>DOResolver: resolveAlwaysDraftDefault()
  DOResolver->>ScmStore: getResolvedSettings(owner/repo)
  ScmStore-->>DOResolver: ScmSettings<br/>{ alwaysUseDraftMode }
  DOResolver-->>Service: alwaysDraft boolean
  Service->>GitHub: createPullRequest<br/>({ draft: alwaysDraft OR input.draft })
  GitHub-->>Service: PR response
  Service-->>Handler: created PR
  Handler-->>Proxy: 200 response
  Proxy-->>SandboxTool: PR result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • feat: Github Setting for 'draft' mode #762: This PR directly implements draft PR mode support by threading a draft parameter from the sandbox tool through the HTTP proxy layers, PR service, and settings resolution — which is exactly the feature described in the linked issue.

Possibly related PRs

  • ColeMurray/background-agents#440: Modifies IntegrationSettingsStore internals including validateAndNormalizeSettings, overlapping with the generic constraint changes in this PR.
  • ColeMurray/background-agents#693: Also modifies IntegrationSettingsStore's sandbox normalization and validation paths in the same file changed by this PR.

Suggested reviewers

  • ColeMurray

Poem

🐇 A draft PR hops into view,
With alwaysUseDraftMode brand new,
The settings store saves each flag with care,
While the web UI toggles fill the air —
No more accidental merges, hooray! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'feat: Github Setting for 'draft' mode' is misleading since the actual implementation is a generic SCM (source-control management) setting, not GitHub-specific, as evidenced by the PR objectives and all changes using 'scm' naming. Update the title to reflect the actual scope, such as 'feat: Add SCM settings for draft mode' or 'feat: SCM integration setting for draft mode defaults' to accurately represent the generic source-control feature.
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 (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

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

@jehiah jehiah marked this pull request as ready for review June 18, 2026 02:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/control-plane/src/routes/session-runtime-proxy.ts`:
- Around line 97-99: The validation check for the draft field in the if
statement starting at line 97 currently allows null values to pass through
because the condition uses != null, which evaluates to false when draft is null
(since null equals null). To reject null and only allow the field to be either
undefined or a boolean, change the validation condition to explicitly check that
draft is either undefined or a boolean type, treating null as invalid input.
This will ensure that line 109 never forwards a null value downstream for the
draft field.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b44d3f64-8c9a-42de-a3f8-05f3d3e141f0

📥 Commits

Reviewing files that changed from the base of the PR and between b712c31 and b810f38.

📒 Files selected for processing (13)
  • packages/control-plane/src/db/integration-settings.test.ts
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/routes/integration-settings.ts
  • packages/control-plane/src/routes/session-runtime-proxy.test.ts
  • packages/control-plane/src/routes/session-runtime-proxy.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/session/http/handlers/pull-request.handler.ts
  • packages/control-plane/src/session/pull-request-service.test.ts
  • packages/control-plane/src/session/pull-request-service.ts
  • packages/sandbox-runtime/src/sandbox_runtime/plugins/inspect-plugin.js
  • packages/shared/src/types/integrations.ts
  • packages/web/src/app/(app)/settings/integrations/[id]/page.tsx
  • packages/web/src/components/settings/integrations/github-pr-integration-settings.tsx

Comment thread packages/control-plane/src/routes/session-runtime-proxy.ts Outdated

@ColeMurray ColeMurray left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

[Automated Review] Summary

Draft-mode threading is correct end-to-end (sandbox tool → proxy → handler → service → provider) and well covered by tests at the store/proxy/service layers. The provider boundary already honored draft (GitHub requestBody.draft, GitLab Draft: title prefix), so this is low-risk plumbing plus a settings surface. No blocking bugs. Verdict: Comment — the main thing worth resolving before merge is the "GitHub" / "GitHub Bot" naming collision (see inline).

Two findings touch unchanged files, so they can't be left inline:

  • [Reliability] source-control/providers/github-provider.ts (if (config.draft) requestBody.draft = true): GitHub rejects draft PRs on private repos under free plans (HTTP 422), with no fallback here. Enabling the global default could break PR creation for a subset of repos. Worth a note in the setting's help text and/or a graceful retry-without-draft on 422.
  • [Testing] github-provider.test.ts has zero tests for createPullRequest, so the GitHub draft path this feature now depends on is unverified at the provider layer (service tests mock the provider). GitLab has three draft tests; GitHub should have at least one.

Inline comments below are prefixed [Automated Review].

Comment thread packages/shared/src/types/integrations.ts Outdated
Comment thread packages/control-plane/src/session/pull-request-service.ts Outdated
Comment thread packages/control-plane/src/routes/integration-settings.ts Outdated
);
}

function Section({

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

[Automated Review] [Maintainability] This component is a near-verbatim copy of code-server-integration-settings.tsx — this Section helper is now duplicated across three integration components, along with the whole save/reset/dirty/toast loop. Not introduced solely by this PR, but each new integration deepens the duplication. A shared useIntegrationSettings hook + shared Section/RepoOverrideRow would shrink this file substantially and keep the integrations consistent.

Comment thread packages/control-plane/src/routes/session-runtime-proxy.ts Outdated
const [showResetDialog, setShowResetDialog] = useState(false);

useEffect(() => {
if (settings !== undefined && !initialized) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

[Automated Review] [Nit] The parent gates rendering on globalLoading/repoSettingsLoading and only mounts this section once data is resolved, so the useState initializer already captures the right value — this initialized/useEffect re-sync is redundant (and won't reflect later SWR revalidations). Harmless, and it mirrors the existing pattern, so fine to leave.

@ColeMurray

ColeMurray commented Jun 18, 2026

Copy link
Copy Markdown
Owner

Some use-case questions here:

In the original implementation, this still allows for the agent to override the draft PR setting via an input (this may be a bug). Is the intent of this change to have this ALWAYS be draft mode when selected, or if not explicitly provided.

Related, I'm onboard with adding the optional parameter to the tool as this make sense. Would the intent of this functionality be better served by custom instructions to the agent, as it seems like it may create confusion to the agent, where the setting is hard-coded upstream to "always draft" while the agent itself called it with draft:false and may lead to it using GH cli to then alter it out of draft state.

Related, I don't think putting this under Github PR makes sense, as the draft PR is an source control provider level concept, rather than specific to github. This is evident by the generic SCM provider types being updated.
It may make more sense here to relax the naming around this to just "Pull request" or "SCM settings" / similar.

ColeMurray

This comment was marked as duplicate.

@jehiah

jehiah commented Jun 18, 2026

Copy link
Copy Markdown
Author

Some use-case questions here:

In the original implementation, this still allows for the agent to override the draft PR setting via an input (this may be a bug). Is the intent of this change to have this ALWAYS be draft mode when selected, or if not explicitly provided.

Yes - this is a bug. My intention was a "force" option (but if you have a strong preference for a "default to draft mode" I could go that direction).

Related, I'm onboard with adding the optional parameter to the tool as this make sense. Would the intent of this functionality be better served by custom instructions to the agent, as it seems like it may create confusion to the agent, where the setting is hard-coded upstream to "always draft" while the agent itself called it with draft:false and may lead to it using GH cli to then alter it out of draft state.

Yes my desire is to make the draft functionality generally exposed so tooling is flexible enough to enable guide particular workflows one direction or the other they could (i.e. open draft mode if tests have not been run, etc) - assuming not everyone will want to force it like I do. I think that has value but I've found limited success and at Bitly we prefer to always start as a draft. You can always flip to non-draft but it's not reversible - you can't undo automated review requests that are sent when you don't start in draft mode.

There is a little bit of conflict in exposing a draft option in the tool if it's forced for a particular repo/setup - I think that's ok and won't cause problems - but happy to iterate if it does. I'll update the tool settings to imply that the draft option may be overridden by a remote policy.

Related, I don't think putting this under Github PR makes sense, as the draft PR is an source control provider level concept, rather than specific to github. This is evident by the generic SCM provider types being updated. It may make more sense here to relax the naming around this to just "Pull request" or "SCM settings" / similar.

I'm totally open on where to set this. I was thinking before i started it would be Github specific - but GitLab supports draft mode too so as you say a top level "SCM settings" or "[Github / GitLab] SCM" might be appropriate. I've been noodling on how to support repositories that use a fork workflow and that setting structure would be a good home for that too.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/control-plane/src/session/pull-request-service.test.ts`:
- Around line 291-298: The test assertion at line 298 expects draft to be true
when alwaysDraftDefault is enabled, but this contradicts the documented
draft-resolution contract where explicit draft parameter should take precedence.
In the test for "forces draft when always-draft is enabled, even if the request
sets draft=false", change the assertion in the expect.objectContaining call to
expect draft: false instead of draft: true, since the explicit draft: false
passed in createInput should override the alwaysDraftDefault setting according
to the documented behavior. Additionally, update the test name and any related
test logic to reflect that alwaysDraftDefault should only apply when draft is
omitted, not when it is explicitly set.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a73d546d-c641-4157-8251-2121367679f7

📥 Commits

Reviewing files that changed from the base of the PR and between b810f38 and 2be4306.

📒 Files selected for processing (7)
  • packages/control-plane/src/routes/integration-settings.ts
  • packages/control-plane/src/routes/session-runtime-proxy.ts
  • packages/control-plane/src/session/pull-request-service.test.ts
  • packages/control-plane/src/session/pull-request-service.ts
  • packages/control-plane/src/source-control/providers/github-provider.test.ts
  • packages/sandbox-runtime/src/sandbox_runtime/plugins/inspect-plugin.js
  • packages/web/src/components/settings/integrations/github-pr-integration-settings.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • packages/control-plane/src/routes/session-runtime-proxy.ts
  • packages/sandbox-runtime/src/sandbox_runtime/plugins/inspect-plugin.js
  • packages/control-plane/src/session/pull-request-service.ts
  • packages/web/src/components/settings/integrations/github-pr-integration-settings.tsx

Comment thread packages/control-plane/src/session/pull-request-service.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/shared/src/types/integrations.ts (1)

49-51: 🏗️ Heavy lift

Rename alwaysUseDraftMode to match its default-only behavior.

The name reads like an enforced policy, but runtime behavior treats it as a default that explicit tool input can override. Renaming now (for example, defaultToDraftMode) would prevent semantics drift across API/UI/DB contracts.

As per coding guidelines, "When threading existing fields through new code paths in TypeScript, evaluate whether the existing design (naming, types, units) is correct and fix bad names or units in the same change rather than propagating problems."

🤖 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 `@packages/shared/src/types/integrations.ts` around lines 49 - 51, The property
`alwaysUseDraftMode` in the ScmSettings interface should be renamed to better
reflect its actual behavior as a default setting rather than an enforced policy.
Rename `alwaysUseDraftMode` to `defaultToDraftMode` (or similar alternative that
clearly indicates it is a default rather than an always-enforced behavior) in
the ScmSettings interface and update all references to this property throughout
the codebase to use the new name consistently.

Source: Coding guidelines

🤖 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 `@packages/control-plane/src/db/integration-settings.ts`:
- Around line 222-224: The validateScmSettings method currently validates
alwaysUseDraftMode but accepts enabledRepos even though it is ignored by
downstream SCM resolution, creating a misleading API contract. Modify the
validateScmSettings method to explicitly reject or throw an error when
enabledRepos is provided in the ScmSettings object, ensuring that only supported
configuration fields are accepted and preventing silent failures where accepted
config has no effect.

In `@packages/web/src/components/settings/scm-settings.tsx`:
- Line 238: The `split("/")` approach at lines 235, 304, and 330 in
scm-settings.tsx incorrectly assumes a single slash separator in the `fullName`,
causing it to fail for nested GitLab namespaces like `group/subgroup/repo`.
Change the parsing logic to extract the repository name from the rightmost
segment using `pop()` and construct the owner from all remaining segments by
joining them back together with "/". This approach will correctly handle
arbitrarily nested namespace structures and ensure the URL path matches the
expected API route pattern. Apply this same fix pattern at lines 308 and 333
where the same parsing logic appears.

---

Nitpick comments:
In `@packages/shared/src/types/integrations.ts`:
- Around line 49-51: The property `alwaysUseDraftMode` in the ScmSettings
interface should be renamed to better reflect its actual behavior as a default
setting rather than an enforced policy. Rename `alwaysUseDraftMode` to
`defaultToDraftMode` (or similar alternative that clearly indicates it is a
default rather than an always-enforced behavior) in the ScmSettings interface
and update all references to this property throughout the codebase to use the
new name consistently.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab7d42b8-71bc-4acb-bec4-342bf95e503e

📥 Commits

Reviewing files that changed from the base of the PR and between 2be4306 and 7b18336.

📒 Files selected for processing (9)
  • packages/control-plane/src/db/integration-settings.test.ts
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/routes/integration-settings.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/control-plane/src/session/pull-request-service.ts
  • packages/shared/src/types/integrations.ts
  • packages/web/src/app/(app)/settings/page.tsx
  • packages/web/src/components/settings/scm-settings.tsx
  • packages/web/src/components/settings/settings-nav.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/control-plane/src/session/pull-request-service.ts

Comment thread packages/control-plane/src/db/integration-settings.ts Outdated
Comment thread packages/web/src/components/settings/scm-settings.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 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 `@packages/control-plane/src/db/scm-settings.ts`:
- Around line 22-29: The validateScmSettings function only validates the type of
the alwaysUseDraftMode field but does not enforce a strict object shape,
allowing extra keys and non-plain objects to be accepted from API payloads.
Enhance the validateScmSettings function to verify that the input is a plain
object, that only expected keys (like alwaysUseDraftMode) are present, and
reject any unexpected properties. Apply the same strict shape validation in the
setGlobal method referenced at lines 48-53 to ensure that global config keys are
restricted to the supported contract and prevent non-plain objects from being
persisted.

In `@packages/control-plane/src/routes/scm-settings.ts`:
- Around line 25-37: The handleGetGlobal function does not catch potential
database errors from store.getGlobal(), causing them to propagate as generic 500
errors. Wrap the store.getGlobal() call in a try-catch block and return a 503
status code response when a database error is caught. Apply the same error
handling pattern to handleListRepoSettings function which has the same
vulnerability with store.listRepoSettings().
- Around line 52-54: The validation check for body.settings only uses typeof to
verify it is an object, but this allows arrays to pass since typeof returns
"object" for arrays. Strengthen the validation condition in the if statement
(that currently checks typeof body.settings !== "object") by adding an
additional check to exclude arrays. Use Array.isArray() to ensure that if
body.settings is an array, the validation fails and returns the error response,
preventing invalid array payloads from being persisted.

In `@packages/web/src/app/api/scm-settings/repos/`[owner]/[name]/route.ts:
- Around line 18-32: The current error handling in the try-catch block returns a
500 status code for all errors, including malformed JSON bodies which should
return a 400 status code. Separate the JSON parsing from other operations by
wrapping the await request.json() call in its own try-catch block, so that JSON
parsing errors can be caught and handled separately to return a 400 status code
with an appropriate error message for bad requests, while keeping the 500 status
for other unexpected errors from the controlPlaneFetch call.

In `@packages/web/src/app/api/scm-settings/route.ts`:
- Around line 29-40: The catch block currently treats all errors the same and
returns a 500 status code, but JSON parsing errors from request.json() should
return a 400 status since they represent client input errors rather than server
failures. Modify the catch block to check if the error is a JSON parsing error
(typically a SyntaxError) and return a 400 status with an appropriate error
message for those cases, while keeping the 500 status for other unexpected
server errors. You can distinguish JSON parsing errors by checking the error
type or message when request.json() fails to parse the body.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a83751ae-3672-48f5-a050-8e150665f5af

📥 Commits

Reviewing files that changed from the base of the PR and between 7b18336 and e74aafe.

📒 Files selected for processing (11)
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/db/scm-settings.test.ts
  • packages/control-plane/src/db/scm-settings.ts
  • packages/control-plane/src/router.ts
  • packages/control-plane/src/routes/scm-settings.ts
  • packages/control-plane/src/session/durable-object.ts
  • packages/shared/src/types/integrations.ts
  • packages/web/src/app/api/scm-settings/repos/[owner]/[name]/route.ts
  • packages/web/src/app/api/scm-settings/repos/route.ts
  • packages/web/src/app/api/scm-settings/route.ts
  • packages/web/src/components/settings/scm-settings.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/components/settings/scm-settings.tsx

Comment thread packages/control-plane/src/db/scm-settings.ts Outdated
Comment thread packages/control-plane/src/routes/scm-settings.ts
Comment thread packages/control-plane/src/routes/scm-settings.ts Outdated
Comment thread packages/web/src/app/api/scm-settings/repos/[owner]/[name]/route.ts
Comment thread packages/web/src/app/api/scm-settings/route.ts
@jehiah jehiah force-pushed the github_draft_mode_762 branch from 6f2946b to 4dfecfb Compare June 22, 2026 13:24
@jehiah

jehiah commented Jun 22, 2026

Copy link
Copy Markdown
Author

RFRR @ColeMurray

image

@jehiah jehiah requested a review from ColeMurray June 22, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants