Skip to content

feat(github-bot): UI-driven model selector with inline directive override#619

Open
donnfelker wants to merge 2 commits into
ColeMurray:mainfrom
donnfelker:upstream-github-bot-model-selector
Open

feat(github-bot): UI-driven model selector with inline directive override#619
donnfelker wants to merge 2 commits into
ColeMurray:mainfrom
donnfelker:upstream-github-bot-model-selector

Conversation

@donnfelker
Copy link
Copy Markdown
Contributor

@donnfelker donnfelker commented May 10, 2026

Summary

  • Adds a per-installation model selector to the GitHub bot integration settings UI, with an opt-in toggle that allows @<bot> model: <alias> reasoning: <effort> inline directives in PR comments to override the configured model for a single run.
  • Introduces shared model resolution helpers (MODEL_OPTIONS, MODEL_ALIASES, resolveModelAlias, normalizeModelId) in @open-inspect/shared so the GitHub and Linear bots share one source of truth for model ids, aliases, and reasoning-effort compatibility.
  • Persists the integration's model and reasoningEffort selection in the control-plane's integration-settings store, plus a boolean allowInlineDirectiveOverride flag.
  • Mirrors the inline-directive parser as packages/github-bot/src/inline-directive.ts and a near-clone of the Linear bot's resolver as packages/github-bot/src/model-resolution.ts (kept separate so the two bots' policies can drift).
  • Documents the end-to-end "how do I add or remove a model" workflow in docs/MODELS.md.

Screenshots

CleanShot 2026-05-10 at 18 55 31@2x CleanShot 2026-05-10 at 18 56 06@2x

Summary by CodeRabbit

  • New Features

    • Support for inline directives in GitHub comments to override model and reasoning effort per-comment
    • Global default model and reasoning effort configuration options
    • Toggle to enable/disable per-comment directive overrides
    • Friendly model aliases (haiku, sonnet, opus) for easier reference
  • Documentation

    • Added comprehensive guide for model configuration and management

Review Change Stack

…ride

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4c5714cc-d2b9-46d1-b69e-602c51c7f95d

📥 Commits

Reviewing files that changed from the base of the PR and between e27b56b and f010918.

📒 Files selected for processing (3)
  • packages/github-bot/test/inline-directive.test.ts
  • packages/github-bot/test/model-resolution.test.ts
  • packages/shared/src/models.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/shared/src/models.ts

📝 Walkthrough

Walkthrough

Adds inline directive parsing for GitHub bot comments to override session model and reasoning effort settings. Introduces shared model alias resolution, updates configuration schemas across control-plane and integration packages, integrates directive processing into GitHub event handlers, and extends web UI settings for managing defaults and override toggles.

Changes

Inline Directive Override Feature

Layer / File(s) Summary
Shared Model Alias Resolution
packages/shared/src/models.ts
Adds internal MODEL_ALIASES map for case-insensitive family shortcuts and bare Anthropic version strings, plus exported resolveModelAlias() function for consistent alias resolution.
Configuration Schema Updates
packages/shared/src/types/integrations.ts, packages/github-bot/src/utils/integration-config.ts
Extends GitHubBotSettings and ResolvedGitHubConfig with allowInlineDirectiveOverride boolean field; adds to fail-closed defaults and config fetching paths with true as the default.
Inline Directive Parsing
packages/github-bot/src/inline-directive.ts
Implements parseInlineDirective() to extract model: and reasoning: tokens via regex boundary checks, normalize/validate using shared helpers, strip all directive tokens from body, and return ParsedDirective with cleaned body and optional overrides.
Session Model & Reasoning Resolution
packages/github-bot/src/model-resolution.ts
Exports ResolveInput and ResolvedModelSettings interfaces; implements resolveSessionModelSettings() with ordered precedence (directive → config → env) and reasoning compatibility validation.
Handler Integration: Directive Parsing & Model Resolution
packages/github-bot/src/handlers.ts
Updates event handlers to parse inline directives from comment bodies, resolve session model settings via the new resolver, use cleaned directive body as prompt input, and log observability fields indicating directive application.
Control-Plane Settings Validation & Response
packages/control-plane/src/db/integration-settings.ts, packages/control-plane/src/routes/integration-settings.ts
Adds validation for allowInlineDirectiveOverride boolean in GitHub settings normalization and includes the field in resolved config responses with default true.
Web UI Settings for Defaults & Override Toggle
packages/web/src/components/settings/integrations/github-integration-settings.tsx
Extends settings UI with model selector, model-driven reasoning effort selector, and allowInlineDirectiveOverride toggle; updates save/reset flows to handle new fields.
Linear Bot Model Resolution Refactor
packages/linear-bot/src/model-resolution.ts, packages/linear-bot/src/__tests__/pure-functions.test.ts
Replaces hardcoded MODEL_LABEL_MAP with shared resolveModelAlias() and isValidModel() helpers; updates test expectations to reflect latest model versions.
Tests & Documentation
packages/github-bot/test/inline-directive.test.ts, packages/github-bot/test/model-resolution.test.ts, packages/github-bot/test/handlers.test.ts, packages/github-bot/test/integration-config.test.ts, packages/control-plane/test/integration/integration-settings.test.ts, docs/MODELS.md
Comprehensive test coverage for directive parsing (tokenization, normalization, boundary handling), model resolution precedence and compatibility, handler integration with override control, config validation, and model onboarding workflows in documentation.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • ColeMurray

🐰 Model directives now flow through comments with ease,
Aliases resolve to the latest with peace—
GitHub bots parse your opus: request,
Reasoning customized puts features to the test,
Web settings control when these overrides bless!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the primary change: adding a UI-driven model selector and inline directive override feature for the GitHub bot integration.
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.

Copy link
Copy Markdown

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

🤖 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/github-bot/src/inline-directive.test.ts`:
- Line 1: Move the test file inline-directive.test.ts out of the package source
folder into the package's test directory (i.e., relocate it from the src tree
into test/), update any relative import paths inside the file to reflect the new
location, and ensure the package's test discovery (vitest/glob or package.json
test config) still includes test/*.test.ts so this file is picked up; no code
changes to tests themselves are required beyond fixing imports/paths.

In `@packages/github-bot/src/model-resolution.test.ts`:
- Line 1: The test file model-resolution.test.ts is placed under src/ but should
live in the package test folder; move the file from
packages/github-bot/src/model-resolution.test.ts to
packages/github-bot/test/model-resolution.test.ts and update any import paths if
necessary (ensure the vitest imports: describe, it, expect remain valid and any
relative imports inside model-resolution.test.ts still resolve after
relocation).

In `@packages/github-bot/src/model-resolution.ts`:
- Around line 64-66: The current early return when modelFromDirective is true
always sets reasoningEffort: null and prevents the config fallback; change the
logic so that if modelFromDirective is true you only return immediately when the
directive explicitly provided a reasoning value (use that value for
reasoningEffort), otherwise do not override reasoningEffort and allow the
subsequent config fallback to run. Specifically, adjust the branch around
modelFromDirective to check for the presence/definedness of the directive's
reasoning (instead of unconditionally assigning null), returning { model,
reasoningEffort: <directiveReasoning> } only when that reasoning is present.

In `@packages/shared/src/models.ts`:
- Around line 206-223: The fallback in resolveModelAlias passes the original raw
string to normalizeModelId which does case-sensitive prefix checks, so
mixed-case inputs like "GPT-5.4" bypass normalization; update resolveModelAlias
to call normalizeModelId with the already-lowercased variable (use lower instead
of raw) so normalizeModelId sees canonical lowercase prefixes and correctly
normalizes bare "claude-"/"gpt-" ids.
🪄 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: 1e99d115-f7b6-4a19-a131-76e0f6118ff1

📥 Commits

Reviewing files that changed from the base of the PR and between 4f8993a and e27b56b.

📒 Files selected for processing (17)
  • docs/MODELS.md
  • packages/control-plane/src/db/integration-settings.ts
  • packages/control-plane/src/routes/integration-settings.ts
  • packages/control-plane/test/integration/integration-settings.test.ts
  • packages/github-bot/src/handlers.ts
  • packages/github-bot/src/inline-directive.test.ts
  • packages/github-bot/src/inline-directive.ts
  • packages/github-bot/src/model-resolution.test.ts
  • packages/github-bot/src/model-resolution.ts
  • packages/github-bot/src/utils/integration-config.ts
  • packages/github-bot/test/handlers.test.ts
  • packages/github-bot/test/integration-config.test.ts
  • packages/linear-bot/src/__tests__/pure-functions.test.ts
  • packages/linear-bot/src/model-resolution.ts
  • packages/shared/src/models.ts
  • packages/shared/src/types/integrations.ts
  • packages/web/src/components/settings/integrations/github-integration-settings.tsx

Comment thread packages/github-bot/test/inline-directive.test.ts
Comment thread packages/github-bot/test/model-resolution.test.ts
Comment on lines +64 to +66
if (modelFromDirective) {
return { model, reasoningEffort: null };
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot May 10, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Model-only directives currently null out reasoning unintentionally

On Line 64, modelFromDirective triggers reasoningEffort: null even when no directive reasoning was supplied. That skips the Line 69 config fallback and is broader than the comment on Lines 62-63.

Proposed fix
-  if (modelFromDirective) {
+  if (modelFromDirective && input.directiveReasoningEffort) {
     return { model, reasoningEffort: null };
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (modelFromDirective) {
return { model, reasoningEffort: null };
}
if (modelFromDirective && input.directiveReasoningEffort) {
return { model, reasoningEffort: null };
}
🤖 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/github-bot/src/model-resolution.ts` around lines 64 - 66, The
current early return when modelFromDirective is true always sets
reasoningEffort: null and prevents the config fallback; change the logic so that
if modelFromDirective is true you only return immediately when the directive
explicitly provided a reasoning value (use that value for reasoningEffort),
otherwise do not override reasoningEffort and allow the subsequent config
fallback to run. Specifically, adjust the branch around modelFromDirective to
check for the presence/definedness of the directive's reasoning (instead of
unconditionally assigning null), returning { model, reasoningEffort:
<directiveReasoning> } only when that reasoning is present.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Declining this change — the current behavior is intentional and is covered by an explicit test in model-resolution.test.ts ("directive model with no directive reasoning: drops config reasoning entirely"). The design choice: when a user supplies a fresh model via inline directive, we deliberately do not carry the previous config reasoning over, because reasoning is tightly coupled to the model and the override is treated as a clean reset. Adopting the suggested fallback would require updating both the test and the surrounding doc comment to flip the documented contract.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@donnfelker, understood — thank you for the detailed explanation. The "clean reset" contract makes good sense: since reasoning effort is model-specific, inheriting config reasoning when the user explicitly switches models via directive could silently apply an incompatible or unexpected effort level. The existing test case documents this contract clearly, so no change is needed here.

( ´・‿・`) ( )


✏️ Learnings added
Learnt from: donnfelker
Repo: ColeMurray/background-agents PR: 619
File: packages/github-bot/src/model-resolution.ts:64-66
Timestamp: 2026-05-14T19:35:18.813Z
Learning: In `packages/github-bot/src/model-resolution.ts`, when a user supplies a model via an inline directive (`modelFromDirective = true`) but provides no directive reasoning effort, the resolver intentionally returns `reasoningEffort: null` and does NOT fall back to `configReasoningEffort`. This is by design — a directive model override is treated as a "clean reset" because reasoning effort is tightly coupled to the model. This contract is covered by the test "directive model with no directive reasoning: drops config reasoning entirely" in `packages/github-bot/src/model-resolution.test.ts`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment thread packages/shared/src/models.ts
Move github-bot test files to test/ to match the per-package
convention documented in CLAUDE.md, and fix a case-sensitivity bug
in resolveModelAlias where the raw (non-lowercased) input was
passed to normalizeModelId, causing mixed-case bare ids like
"GPT-5.4" to skip prefix normalization.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.

1 participant