feat(git-providers): native GitHub integration via Decobot App#3475
feat(git-providers): native GitHub integration via Decobot App#3475vibegui wants to merge 1 commit into
Conversation
Replaces the legacy shared-token `deco/mcp-github` MCP connection (where
every org member acted on GitHub as the connection creator) with a first-
class Git Provider subsystem that gives each Studio user proper GitHub
identity attribution.
Key pieces:
- Decobot GitHub App credentials read from DECOBOT_APP_ID, DECOBOT_PRIVATE_KEY
(PEM or DECOBOT_PRIVATE_KEY_BASE64), DECOBOT_CLIENT_ID, DECOBOT_CLIENT_SECRET,
DECOBOT_APP_SLUG. Self-hosters bring their own App; the adapter reports
available=false when env is missing.
- New `git_provider_installations` table (migration 093) recording which
GitHub accounts each org has installed Decobot on. Holds no secrets —
installation tokens are short-lived and minted on demand.
- `GitProviderFactory.resolveClient(ctx, { owner })` implements the
four-quadrant identity matrix:
real user + linked GitHub → user-to-server token (action attributed
to the calling user on GitHub)
real user + unlinked → throws GitProviderUserLinkRequiredError
with a CTA URL (no silent bot fallback)
no user (cron, event-bus) → installation token (Decobot), with a
"via Decobot — Studio request <id>" footer
on write payloads
- Better Auth `socialProviders.github` falls back to DECOBOT_CLIENT_ID/
CLIENT_SECRET so the user-link flow works out of the box.
- New management tools: GIT_PROVIDERS_LIST, GIT_PROVIDER_INSTALL_URL,
GIT_PROVIDER_INSTALL_COMPLETE, GIT_PROVIDER_INSTALLATION_LIST,
GIT_PROVIDER_INSTALLATION_DELETE, GIT_PROVIDER_USER_LINK_STATUS.
- New native GitHub tools, always registered in CORE_TOOLS, routed through
the factory: GITHUB_READ_FILE, GITHUB_LIST_REPO_CONTENTS,
GITHUB_CREATE_ISSUE, GITHUB_COMMENT, GITHUB_LIST_PRS, GITHUB_READ_PR.
- Settings → Git Providers UI: install dialog (popup + postMessage),
installations list, per-user link card. New /oauth/callback/git-provider
route handles the GitHub App install redirect.
- Tests cover RS256 App JWT signing (signature verified against a
generated keypair), installation-token cache TTL + concurrent-refresh
dedup, and the full four-quadrant matrix in resolveClient.
Legacy `deco/mcp-github` connection and `GITHUB_LIST_USER_ORGS` left in
place for now; deprecation is a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
🧪 BenchmarkShould we run the Virtual MCP strategy benchmark for this PR? React with 👍 to run the benchmark.
Benchmark will run on the next push after you react. |
Release OptionsSuggested: Minor ( React with an emoji to override the release type:
Current version:
|
There was a problem hiding this comment.
2 issues found
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/mesh/src/tools/github/list-repo-contents.ts">
<violation number="1" location="apps/mesh/src/tools/github/list-repo-contents.ts:24">
P2: Require non-empty `owner` and `repo` in the input schema so invalid GitHub requests are rejected early.
(Based on your team's feedback about validating string fields strictly.) [FEEDBACK_USED]</violation>
</file>
<file name="apps/mesh/migrations/093-git-provider-installations.ts">
<violation number="1" location="apps/mesh/migrations/093-git-provider-installations.ts:32">
P2: `organization_id + provider_id + account_login` should be unique to prevent duplicate installation rows for the same account and ambiguous account-based lookups.</violation>
</file>
Tip: cubic used a learning from your PR history. Let your coding agent read cubic learnings directly with the cubic MCP.
Re-trigger cubic
| openWorldHint: true, | ||
| }, | ||
| inputSchema: z.object({ | ||
| owner: z.string(), |
There was a problem hiding this comment.
P2: Require non-empty owner and repo in the input schema so invalid GitHub requests are rejected early.
(Based on your team's feedback about validating string fields strictly.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/src/tools/github/list-repo-contents.ts, line 24:
<comment>Require non-empty `owner` and `repo` in the input schema so invalid GitHub requests are rejected early.
(Based on your team's feedback about validating string fields strictly.) </comment>
<file context>
@@ -0,0 +1,78 @@
+ openWorldHint: true,
+ },
+ inputSchema: z.object({
+ owner: z.string(),
+ repo: z.string(),
+ path: z.string().default("").describe("Directory path (empty = root)."),
</file context>
| .execute(); | ||
|
|
||
| await db.schema | ||
| .createIndex("idx_git_provider_installations_org_account") |
There was a problem hiding this comment.
P2: organization_id + provider_id + account_login should be unique to prevent duplicate installation rows for the same account and ambiguous account-based lookups.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/mesh/migrations/093-git-provider-installations.ts, line 32:
<comment>`organization_id + provider_id + account_login` should be unique to prevent duplicate installation rows for the same account and ambiguous account-based lookups.</comment>
<file context>
@@ -0,0 +1,47 @@
+ .execute();
+
+ await db.schema
+ .createIndex("idx_git_provider_installations_org_account")
+ .on("git_provider_installations")
+ .columns(["organization_id", "provider_id", "account_login"])
</file context>
Summary
Replaces the legacy shared-token
deco/mcp-githubMCP connection — where every org member acted on GitHub as the connection creator — with a first-class Git Provider subsystem backed by a Decobot GitHub App.The change fixes two problems:
apps/mesh/src/mcp-clients/outbound/headers.ts:104-108).After this PR, GitHub actions are attributed to the calling Studio user whenever they trigger an agent. Unattended runs (cron, event-bus) act as Decobot.
How identity is resolved
Every native GitHub tool routes through
ctx.gitProviders.resolveClient(ctx, { owner }), which implements:accountrow to mint a user-to-server token. Actions show as the user on GitHub.GitProviderUserLinkRequiredError(codeGIT_PROVIDER_USER_LINK_REQUIRED) with a link CTA URL. No silent fallback to the bot token — that would defeat the point of the feature.via Decobot — Studio request <id>footer so attribution stays traceable on GitHub itself.What ships
Config —
DECOBOT_APP_ID,DECOBOT_PRIVATE_KEY(PEM orDECOBOT_PRIVATE_KEY_BASE64),DECOBOT_CLIENT_ID,DECOBOT_CLIENT_SECRET,DECOBOT_APP_SLUG. Self-hosters bring their own App; the adapter reportsavailable=falsewhen env is missing and the UI shows a "not configured" message.Schema — new
git_provider_installationstable (migration093). Holds GitHub account metadata only — installation tokens are short-lived and minted on demand from the App's private key (no secrets stored).Subsystem —
apps/mesh/src/git-providers/mirrors the shape ofapps/mesh/src/ai-providers/:types.ts—GitProviderAdapter,ResolvedGitClient, structured error classes.registry.ts+factory.ts—GitProviderFactory.resolveClient(the four-quadrant logic above).adapters/github/— RS256 App JWT signing (nojsonwebtokendep), in-memory installation-token cache with concurrent-refresh dedup, octokit-style fetch helpers, Decobot adapter with user-token refresh.Tools — all registered in
CORE_TOOLS:GIT_PROVIDERS_LIST,GIT_PROVIDER_INSTALL_URL,GIT_PROVIDER_INSTALL_COMPLETE,GIT_PROVIDER_INSTALLATION_LIST,GIT_PROVIDER_INSTALLATION_DELETE,GIT_PROVIDER_USER_LINK_STATUS.GITHUB_READ_FILE,GITHUB_LIST_REPO_CONTENTS,GITHUB_CREATE_ISSUE,GITHUB_COMMENT,GITHUB_LIST_PRS,GITHUB_READ_PR.Better Auth —
socialProviders.githubnow falls back toDECOBOT_CLIENT_ID/DECOBOT_CLIENT_SECRETso users can link their personal GitHub via the standard Better Authsign-in/socialendpoint.UI —
Settings → Git Providers:GIT_PROVIDER_INSTALL_COMPLETE)./oauth/callback/git-providerroute handles the App install redirect.Tests — 14 new tests covering:
resolveClient+ edge cases (no org, no installation, unavailable provider, case-insensitive owner lookup). The Q2 test explicitly asserts we throw rather than falling back to the bot — that's the regression-prevention bit.Out of scope (follow-ups)
deco/mcp-githubconnections and the legacyGITHUB_LIST_USER_ORGStool (both still work).Test plan
http://localhost:4000/oauth/callback/git-providerandhttp://localhost:4000/api/auth/callback/github.DECOBOT_*env vars locally.bun run --cwd=apps/mesh migrate && bun run dev.git_provider_installations.accountrow appears withproviderId="github".GITHUB_CREATE_ISSUEagainst the repo. Verify the issue on github.com is opened by your user, not Decobot-dev.accountrow and try again. Verify the tool throwsGIT_PROVIDER_USER_LINK_REQUIREDinstead of silently using anyone else's token.GITHUB_COMMENT. Verify the comment shows as Decobot-dev with the workflow footer.🤖 Generated with Claude Code
Summary by cubic
Adds native GitHub integration via the Decobot App and a first‑class Git Provider subsystem. Fixes identity attribution so GitHub actions run as the calling user; unattended runs use Decobot.
New Features
deco/mcp-githubwith a Git Provider backed by the Decobot GitHub App.GIT_PROVIDER_USER_LINK_REQUIREDwith a link URL.git_provider_installations(migration 093); no secrets stored, tokens minted on demand and cached.GIT_PROVIDERS_LIST,GIT_PROVIDER_INSTALL_URL,GIT_PROVIDER_INSTALL_COMPLETE,GIT_PROVIDER_INSTALLATION_LIST,GIT_PROVIDER_INSTALLATION_DELETE,GIT_PROVIDER_USER_LINK_STATUS.DECOBOT_CLIENT_ID/DECOBOT_CLIENT_SECRETfor the per-user GitHub link flow./oauth/callback/git-provider.Migration
DECOBOT_APP_ID,DECOBOT_PRIVATE_KEYorDECOBOT_PRIVATE_KEY_BASE64,DECOBOT_CLIENT_ID,DECOBOT_CLIENT_SECRET,DECOBOT_APP_SLUG.deco/mcp-githubandGITHUB_LIST_USER_ORGSremain available for now.Written for commit d7eb702. Summary will update on new commits. Review in cubic