Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion github/app.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"scopeName": "deco",
"name": "github-mcp",
"name": "mcp-github",
"friendlyName": "GitHub",
"connection": {
"type": "HTTP",
Expand Down
38 changes: 38 additions & 0 deletions github/server/lib/event-bus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Event Bus reference cache.
*
* Captured during onChange (when the runtime has full state),
* so the webhook handler can publish events without going through
* the authenticated runtime path.
*/

type EventPublishFn = (event: {
type: string;
subject: string;
data?: Record<string, unknown>;
}) => Promise<unknown>;

let cachedPublish: EventPublishFn | null = null;

export function setEventPublisher(publish: EventPublishFn): void {
cachedPublish = publish;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: This singleton publisher will send every webhook through the last connection that ran onChange, so multi-connection installs can publish into the wrong Event Bus context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At github/server/lib/event-bus.ts, line 18:

<comment>This singleton publisher will send every webhook through the last connection that ran `onChange`, so multi-connection installs can publish into the wrong Event Bus context.</comment>

<file context>
@@ -0,0 +1,38 @@
+let cachedPublish: EventPublishFn | null = null;
+
+export function setEventPublisher(publish: EventPublishFn): void {
+  cachedPublish = publish;
+}
+
</file context>
Fix with Cubic

}

export function clearEventPublisher(): void {
cachedPublish = null;
}

export async function publishEvent(event: {
type: string;
subject: string;
data?: Record<string, unknown>;
}): Promise<void> {
if (!cachedPublish) {
console.warn(
"[EventBus] No publisher available — EVENT_BUS not yet configured",
);
return;
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: Returning here silently drops the webhook event. handleGitHubWebhook treats only thrown errors as publish failures, so this path acknowledges the request even though nothing reached the Event Bus.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At github/server/lib/event-bus.ts, line 34:

<comment>Returning here silently drops the webhook event. `handleGitHubWebhook` treats only thrown errors as publish failures, so this path acknowledges the request even though nothing reached the Event Bus.</comment>

<file context>
@@ -0,0 +1,38 @@
+    console.warn(
+      "[EventBus] No publisher available — EVENT_BUS not yet configured",
+    );
+    return;
+  }
+
</file context>
Suggested change
return;
throw new Error("EVENT_BUS not yet configured");
Fix with Cubic

}

await cachedPublish(event);
}
8 changes: 8 additions & 0 deletions github/server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type { Registry } from "@decocms/mcps-shared/registry";
import { serve } from "@decocms/mcps-shared/serve";
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: This webhook path can silently drop valid events until onChange has populated the global publisher cache.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At github/server/main.ts, line 107:

<comment>This webhook path can silently drop valid events until `onChange` has populated the global publisher cache.</comment>

<file context>
@@ -97,7 +104,7 @@ const wrappedFetch: typeof runtime.fetch = async (req, env, ctx) => {
   // GitHub webhook endpoint (unauthenticated — signature-verified instead)
   if (req.method === "POST" && url.pathname === "/webhooks/github") {
-    return handleGitHubWebhook(req, env);
+    return handleGitHubWebhook(req);
   }
 
</file context>
Fix with Cubic

import { withRuntime } from "@decocms/runtime";
import { exchangeCodeForToken } from "./lib/github-client.ts";
import { setEventPublisher } from "./lib/event-bus.ts";
import { captureInstallationMappings } from "./lib/installation-map.ts";
import {
handleProxiedRequest,
Expand Down Expand Up @@ -72,12 +73,19 @@ const runtime = withRuntime<Env, typeof StateSchema, Registry>({
onChange: async (env) => {
invalidateUpstreamCache();

// Capture Event Bus reference for webhook handler
const eventBus = env.MESH_REQUEST_CONTEXT?.state?.EVENT_BUS;
if (eventBus) {
setEventPublisher((event) => eventBus.EVENT_PUBLISH(event));
}

const token = env.MESH_REQUEST_CONTEXT?.authorization;
const connectionId = env.MESH_REQUEST_CONTEXT?.connectionId;
if (token && connectionId) {
await captureInstallationMappings(token, connectionId);
}
},
scopes: ["EVENT_BUS::*"],
state: StateSchema,
},

Expand Down
10 changes: 4 additions & 6 deletions github/server/types/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
*/

import type { Registry } from "@decocms/mcps-shared/registry";
import { type DefaultEnv } from "@decocms/runtime";
import { BindingOf, type DefaultEnv } from "@decocms/runtime";
import { z } from "zod";

/**
* State schema — no user-configurable options needed.
* The upstream URL is always https://api.githubcopilot.com/mcp/.
*/
export const StateSchema = z.object({});
export const StateSchema = z.object({
EVENT_BUS: BindingOf("@deco/event-bus"),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 18, 2026

Choose a reason for hiding this comment

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

P1: This required EVENT_BUS binding will not be available on /webhooks/github, so it adds config surface without enabling the webhook publish path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At github/server/types/env.ts, line 10:

<comment>This required `EVENT_BUS` binding will not be available on `/webhooks/github`, so it adds config surface without enabling the webhook publish path.</comment>

<file context>
@@ -3,14 +3,12 @@
- */
-export const StateSchema = z.object({});
+export const StateSchema = z.object({
+  EVENT_BUS: BindingOf("@deco/event-bus"),
+});
 
</file context>
Fix with Cubic

});

/**
* Environment type combining Deco bindings with shared Registry
Expand Down
17 changes: 15 additions & 2 deletions github/server/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
* GitHub Webhook HTTP Handler
*
* Receives GitHub App webhook events, verifies signatures,
* and routes them to the correct connection.
* and publishes them to the Event Bus.
*/

import { publishEvent } from "./lib/event-bus.ts";
import { getConnectionForInstallation } from "./lib/installation-map.ts";
import { verifyGitHubWebhook } from "./lib/webhook.ts";
import { hasMatchingTrigger } from "./lib/trigger-store.ts";
import { verifyGitHubWebhook } from "./lib/webhook.ts";

const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET || "";

Expand Down Expand Up @@ -49,6 +50,18 @@ export async function handleGitHubWebhook(req: Request): Promise<Response> {
`[Webhook] ${fullEventType} | subject=${subject} | connection=${connectionId} | sender=${payload.sender?.login}`,
);

// Publish to Event Bus
try {
await publishEvent({
type: fullEventType,
subject,
data: payload as Record<string, unknown>,
});
} catch (error) {
console.error("[Webhook] Failed to publish to Event Bus:", error);
return Response.json({ error: "Failed to publish event" }, { status: 502 });
}

if (
hasMatchingTrigger(
fullEventType,
Expand Down
Loading