Skip to content

Pr 3266 fix#3322

Open
deepshekhardas wants to merge 1 commit into
triggerdotdev:mainfrom
deepshekhardas:pr-3266-fix
Open

Pr 3266 fix#3322
deepshekhardas wants to merge 1 commit into
triggerdotdev:mainfrom
deepshekhardas:pr-3266-fix

Conversation

@deepshekhardas

@deepshekhardas deepshekhardas commented Apr 3, 2026

Copy link
Copy Markdown

What

Adds OpenClaw agent integration — a new agent provisioning system with Slack webhooks, agent setup/status pages, and database models.

Changes

New files

  • routes/agents.$agentId.status.tsx — agent status dashboard page
  • routes/agents.setup.tsx — agent setup wizard
  • routes/api.agents.provision.ts — provisioning API
  • routes/webhooks.slack.ts — Slack webhook handler
  • internal-packages/database/prisma/migrations/20260325122458_add_openclaw_agents/migration.sql
  • .changeset/openclaw-agent-integration.md

Schema

  • Added AgentConfig, AgentExecution, AgentHealthCheck models
  • Added agentConfigs relation to User model

Manual resolutions (merge conflicts)

11 files conflicted during merge:

  • LoginPageLayout, CodeBlock, AppLayout, Table, api.v1.artifacts, HelpAndFeedbackPopover: kept main's version (branding/styling)
  • test.tasks route, spans route: kept main's version
  • tailwind.css, tailwind.config.js: kept main's version
  • schema.prisma: merged both sides (added AgentConfig + AgentExecution + AgentHealthCheck alongside main's LLM/PlatformNotification models)

Branding cleanup

Reverted fork-specific branding (AirTrigger → Trigger.dev, airtrigger.dev → trigger.dev) across 53 files.

Notes

Co-authored-by: James Ritchie james@trigger.dev

@deepshekhardas

Copy link
Copy Markdown
Author

@loks0n @ChiragAgg5k this PR has 119 file changes and is 286 commits behind main. Could you provide guidance on whether this should be split into smaller PRs?

@changeset-bot

changeset-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: ac862c6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 25 packages
Name Type
@trigger.dev/core Patch
@trigger.dev/sdk Patch
@trigger.dev/build Patch
trigger.dev Patch
@trigger.dev/plugins Patch
@trigger.dev/python Patch
@trigger.dev/redis-worker Patch
@trigger.dev/schema-to-json Patch
@internal/cache Patch
@internal/clickhouse Patch
@internal/llm-model-catalog Patch
@trigger.dev/rbac Patch
@internal/redis Patch
@internal/replication Patch
@internal/run-engine Patch
@internal/schedule-engine Patch
@internal/testcontainers Patch
@internal/tracing Patch
@internal/tsql Patch
@internal/zod-worker Patch
@internal/sdk-compat-tests Patch
@trigger.dev/react-hooks Patch
@trigger.dev/rsc Patch
@trigger.dev/database Patch
@trigger.dev/otlp-importer Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds an OpenClaw AI agent platform integration consisting of three new database models (AgentConfig, AgentExecution, AgentHealthCheck) with a corresponding Prisma migration, a Remix agent setup route (/agents/setup) with Zod validation and a provisioning call to an internal API endpoint (/api/agents/provision), a Slack webhook handler (/webhooks/slack) that routes messages to agent containers and records executions, and an agent status page (/agents/:agentId/status). Additionally, the PR replaces the LogoIcon and LogoType SVG artwork with simplified designs, changes the scrollbar thumb color from scrollbar-thumb-charcoal-600 to scrollbar-thumb-gray-300 across the entire webapp, and applies minor visual fixes to ErrorDisplay, ChartZoom, and the email footer URL.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Pr 3266 fix' is vague and does not clearly convey the main change (OpenClaw agent integration). Replace with a descriptive title such as 'Add OpenClaw agent integration with Slack webhooks and provisioning system' to clearly communicate the primary feature being introduced.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.50% 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 The PR description comprehensively explains the OpenClaw agent integration, new files, schema changes, conflict resolution approach, and branding cleanup, providing sufficient context for reviewers despite the template not being strictly followed.
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.

@deepshekhardas

Copy link
Copy Markdown
Author

Rebased and conflicts resolved. Also cleaned up fork-specific branding (AirTrigger → Trigger.dev) across all 53 changed files. Ready for review 🙏

Squashed rebase of pr-3266-fix onto official/main.

Co-authored-by: James Ritchie <james@trigger.dev>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

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

⚠️ Outside diff range comments (1)
.changeset/openclaw-agent-integration.md (1)

1-9: ⚠️ Potential issue | 🟡 Minor

Changeset targets incorrect packages with no actual changes.

The changeset specifies @trigger.dev/core and @trigger.dev/sdk as patch versions, but no code changes exist in either packages/core or packages/trigger-sdk. The OpenClaw agent integration changes are only in apps/webapp (internal application), which doesn't publish as an npm package. Either remove this changeset if the changes are internal-only, or update it to target the correct published package.

🧹 Nitpick comments (2)
apps/webapp/app/routes/api.agents.provision.ts (1)

3-3: ⚡ Quick win

Unused import.

spawn from child_process is imported but never used.

-import { spawn } from "child_process";
apps/webapp/app/routes/agents.$agentId.status.tsx (1)

2-3: ⚡ Quick win

Remove unused imports.

json from @remix-run/node and useLoaderData from @remix-run/react are imported but not used. The code uses typedjson and useTypedLoaderData instead.

 import type { LoaderFunctionArgs } from "`@remix-run/node`";
-import { json } from "`@remix-run/node`";
-import { useLoaderData } from "`@remix-run/react`";
 import { typedjson, useTypedLoaderData } from "remix-typedjson";

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5f4db2e7-9540-4365-8aee-06def1775349

📥 Commits

Reviewing files that changed from the base of the PR and between e5fca6b and ac862c6.

📒 Files selected for processing (47)
  • .changeset/openclaw-agent-integration.md
  • apps/webapp/app/components/AskAI.tsx
  • apps/webapp/app/components/ErrorDisplay.tsx
  • apps/webapp/app/components/LogoIcon.tsx
  • apps/webapp/app/components/LogoType.tsx
  • apps/webapp/app/components/code/AIQueryInput.tsx
  • apps/webapp/app/components/code/TSQLEditor.tsx
  • apps/webapp/app/components/code/TSQLResultsTable.tsx
  • apps/webapp/app/components/logs/LogDetailView.tsx
  • apps/webapp/app/components/logs/LogsTable.tsx
  • apps/webapp/app/components/navigation/EnvironmentSelector.tsx
  • apps/webapp/app/components/navigation/OrganizationSettingsSideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenu.tsx
  • apps/webapp/app/components/navigation/SideMenuHeader.tsx
  • apps/webapp/app/components/onboarding/TechnologyPicker.tsx
  • apps/webapp/app/components/primitives/Select.tsx
  • apps/webapp/app/components/primitives/Sheet.tsx
  • apps/webapp/app/components/primitives/TreeView/TreeView.tsx
  • apps/webapp/app/components/primitives/charts/ChartLegendCompound.tsx
  • apps/webapp/app/components/primitives/charts/ChartZoom.tsx
  • apps/webapp/app/components/runs/v3/ReplayRunDialog.tsx
  • apps/webapp/app/components/scheduled/timezones.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.batches.$batchParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.deployments.$deploymentParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.environment-variables.new/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/QueryHelpSidebar.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.query/QueryHistoryPopover.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.tokens.$waitpointParam/route.tsx
  • apps/webapp/app/routes/_app.orgs.$organizationSlug.settings._index/route.tsx
  • apps/webapp/app/routes/_app.timezones/route.tsx
  • apps/webapp/app/routes/agents.$agentId.status.tsx
  • apps/webapp/app/routes/agents.setup.tsx
  • apps/webapp/app/routes/api.agents.provision.ts
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam.streams.$streamKey/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.bulkaction.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.schedules.new/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.waitpoints.$waitpointFriendlyId.complete/route.tsx
  • apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.schedules.new.natural-language.tsx
  • apps/webapp/app/routes/storybook.popover/route.tsx
  • apps/webapp/app/routes/storybook/route.tsx
  • apps/webapp/app/routes/webhooks.slack.ts
  • apps/webapp/remix.config.js
  • internal-packages/database/prisma/migrations/20260325122458_add_openclaw_agents/migration.sql
  • internal-packages/database/prisma/schema.prisma
  • internal-packages/emails/emails/components/Footer.tsx

Comment on lines +9 to +16
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#2E7D32" />
<stop offset="100%" stopColor="#43A047" />
</linearGradient>
</defs>
{/* Icon */}
<rect x="0" y="8" width="64" height="64" rx="14" fill="url(#logoGradient)" />
<path d="M32 20L46 56H18L32 20Z" fill="white" fillOpacity="0.9" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find and read the LogoType.tsx file
fd -t f "LogoType.tsx" apps/

Repository: triggerdotdev/trigger.dev

Length of output: 109


🏁 Script executed:

# Search for other uses of "logoGradient" or similar gradient patterns in the codebase
rg "logoGradient|linearGradient.*id=" --type tsx --type ts

Repository: triggerdotdev/trigger.dev

Length of output: 97


🏁 Script executed:

# Check the current implementation of LogoType.tsx
cat apps/webapp/app/components/LogoType.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 1022


🏁 Script executed:

# Find components that use LogoType to understand if multiple instances appear on same page
rg "LogoType" --type tsx --type ts -B 2 -A 2

Repository: triggerdotdev/trigger.dev

Length of output: 97


🏁 Script executed:

# Search for other gradient patterns and inline SVG IDs (broader search)
rg "id=\"[^\"]*Gradient|url\(#" apps/webapp --type-list

Repository: triggerdotdev/trigger.dev

Length of output: 459


🏁 Script executed:

# Search for logoGradient references
rg "logoGradient" apps/

Repository: triggerdotdev/trigger.dev

Length of output: 310


🏁 Script executed:

# Find imports of LogoType to understand usage patterns
rg "import.*LogoType|from.*LogoType" apps/

Repository: triggerdotdev/trigger.dev

Length of output: 155


🏁 Script executed:

# Check if LoginPageLayout mentioned in review actually uses LogoType
rg "LogoType" apps/webapp/app/components/LoginPageLayout.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 152


🏁 Script executed:

# Find all imports of LogoType across the entire codebase
rg "import.*LogoType|from.*LogoType" apps/

Repository: triggerdotdev/trigger.dev

Length of output: 155


🏁 Script executed:

# Search for other inline SVG patterns with IDs to assess collision risk
rg "id\s*=\s*['\"]" apps/webapp --max-count=50 | head -40

Repository: triggerdotdev/trigger.dev

Length of output: 6339


🏁 Script executed:

# Check if there are other components in same directory that might have SVG elements
ls -la apps/webapp/app/components/ | grep -E "\.(tsx?|jsx?)$" | head -20

Repository: triggerdotdev/trigger.dev

Length of output: 1470


🏁 Script executed:

# Search for any other gradient definitions or SVG markers/patterns
rg "linearGradient|pattern|marker" apps/webapp/app --max-count=20

Repository: triggerdotdev/trigger.dev

Length of output: 50383


🏁 Script executed:

# Check if LogoType is used anywhere else besides LoginPageLayout
rg "LogoType" apps/ -A 3 -B 1 2>/dev/null | head -100

Repository: triggerdotdev/trigger.dev

Length of output: 1234


🏁 Script executed:

# Look at LoginPageLayout to see how LogoType is used
cat apps/webapp/app/components/LoginPageLayout.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 3938


🏁 Script executed:

# Search for any scenarios where the page might render multiple SVGs with inline gradients
rg "linearGradient|<svg" apps/webapp/app/components/LoginPageLayout.tsx

Repository: triggerdotdev/trigger.dev

Length of output: 51


Use useId() for unique SVG gradient IDs in reusable components.

The fixed id="logoGradient" could cause collisions if LogoType is ever rendered multiple times on the same page. While the current usage (single instance in LoginPageLayout) is safe, it's best practice to use React's useId() hook for any IDs in reusable components.

Proposed fix
+import { useId } from "react";
+
 export function LogoType({ className }: { className?: string }) {
+  const gradientId = useId();
   return (
     <svg
       viewBox="0 0 520 80"
       xmlns="http://www.w3.org/2000/svg"
       className={className}
     >
       <defs>
-        <linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="0%">
+        <linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
           <stop offset="0%" stopColor="`#2E7D32`" />
           <stop offset="100%" stopColor="`#43A047`" />
         </linearGradient>
       </defs>
       {/* Icon */}
-      <rect x="0" y="8" width="64" height="64" rx="14" fill="url(`#logoGradient`)" />
+      <rect x="0" y="8" width="64" height="64" rx="14" fill={`url(#${gradientId})`} />
📝 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
<linearGradient id="logoGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="#2E7D32" />
<stop offset="100%" stopColor="#43A047" />
</linearGradient>
</defs>
{/* Icon */}
<rect x="0" y="8" width="64" height="64" rx="14" fill="url(#logoGradient)" />
<path d="M32 20L46 56H18L32 20Z" fill="white" fillOpacity="0.9" />
import { useId } from "react";
export function LogoType({ className }: { className?: string }) {
const gradientId = useId();
return (
<svg
viewBox="0 0 520 80"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<defs>
<linearGradient id={gradientId} x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stopColor="`#2E7D32`" />
<stop offset="100%" stopColor="`#43A047`" />
</linearGradient>
</defs>
{/* Icon */}
<rect x="0" y="8" width="64" height="64" rx="14" fill={`url(#${gradientId})`} />
<path d="M32 20L46 56H18L32 20Z" fill="white" fillOpacity="0.9" />
</svg>
);
}

>
<ResizablePanel id="payload" min="300px">
<div className="rounded-smbg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
<div className="rounded-smbg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore the missing class separator here.

rounded-smbg-charcoal-900 looks like two Tailwind utilities collapsed into one token, so the panel will lose its intended radius/background styling.

Fix
-          <div className="rounded-smbg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300">
+          <div className="rounded-sm bg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300">
📝 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
<div className="rounded-smbg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300">
<div className="rounded-sm bg-charcoal-900 mb-3 h-full min-h-40 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300">

{check.isHealthy ? "✓ Healthy" : "✗ Unhealthy"}
</span>
</TableCell>
<TableCell>{check.responseTimeMs}ms</TableCell>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle null responseTimeMs.

responseTimeMs is nullable in the schema. When null, this renders "nullms".

-                    <TableCell>{check.responseTimeMs}ms</TableCell>
+                    <TableCell>{check.responseTimeMs != null ? `${check.responseTimeMs}ms` : "N/A"}</TableCell>
📝 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
<TableCell>{check.responseTimeMs}ms</TableCell>
<TableCell>{check.responseTimeMs != null ? `${check.responseTimeMs}ms` : "N/A"}</TableCell>

Comment on lines +46 to +48
// Parse tools JSON
const tools = JSON.parse(data.tools || "[]");

Copy link
Copy Markdown
Contributor

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

JSON.parse can throw on malformed input.

data.tools is validated by Zod as a string, but JSON.parse can still throw SyntaxError if the string isn't valid JSON. This would cause an unhandled exception.

🛠️ Suggested fix using Zod transform
 const SetupSchema = z.object({
   agentName: z.string().min(1, "Agent name is required"),
   model: z.enum(["claude-3.5-sonnet", "claude-3-opus", "gpt-4-turbo"]),
   messagingPlatform: z.enum(["slack", "discord", "telegram"]),
-  tools: z.string(), // JSON string array
+  tools: z.string().transform((val, ctx) => {
+    try {
+      const parsed = JSON.parse(val || "[]");
+      if (!Array.isArray(parsed)) {
+        ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Tools must be an array" });
+        return z.NEVER;
+      }
+      return parsed as string[];
+    } catch {
+      ctx.addIssue({ code: z.ZodIssueCode.custom, message: "Invalid JSON" });
+      return z.NEVER;
+    }
+  }),
   slackWorkspaceId: z.string().optional(),
   slackWebhookToken: z.string().optional(),
 });

 // Then in action:
-    // Parse tools JSON
-    const tools = JSON.parse(data.tools || "[]");
+    const tools = data.tools; // Already parsed by Zod

Comment on lines +71 to +85
const provisionResponse = await fetch("http://localhost:3000/api/agents/provision", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ agentId: agentConfig.id }),
});

if (!provisionResponse.ok) {
logger.error("Provisioning failed", {
agentId: agentConfig.id,
status: provisionResponse.status,
});
}
} catch (error) {
logger.error("Failed to call provisioning endpoint", { error });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hardcoded localhost URL won't work in production; provisioning failure leaves orphaned agents.

Two issues:

  1. http://localhost:3000 is hardcoded - this won't work in deployed environments. Use an environment variable via env.server.ts per coding guidelines.
  2. If provisioning fails (Lines 77-85), the agent config is already created and the user is still redirected to the status page. This leaves the agent in an inconsistent state with no way to retry provisioning.
🛠️ Suggested approach
+import { env } from "~/env.server";
+
 // In the action:
-      const provisionResponse = await fetch("http://localhost:3000/api/agents/provision", {
+      const provisionResponse = await fetch(`${env.APP_ORIGIN}/api/agents/provision`, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ agentId: agentConfig.id }),
       });

       if (!provisionResponse.ok) {
         logger.error("Provisioning failed", {
           agentId: agentConfig.id,
           status: provisionResponse.status,
         });
+        // Update status to failed so user can retry
+        await prisma.agentConfig.update({
+          where: { id: agentConfig.id },
+          data: { status: "failed" },
+        });
       }
     } catch (error) {
       logger.error("Failed to call provisioning endpoint", { error });
+      await prisma.agentConfig.update({
+        where: { id: agentConfig.id },
+        data: { status: "failed" },
+      });
     }

Source: Coding guidelines

Comment on lines +15 to +26
try {
const event = await request.json() as any;

// Handle Slack URL verification
if (event.type === "url_verification") {
return json({ challenge: event.challenge });
}

// Handle message events
if (event.type === "event_callback" && event.event.type === "message") {
const slackEvent = event.event;
const workspaceId = event.team_id;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Missing Slack request signature verification and input validation.

  1. Security: Slack webhooks should verify the X-Slack-Signature header to prevent forged requests. Without this, anyone can call this endpoint pretending to be Slack.
  2. Validation: The payload is cast as any without validation. Line 24 accesses event.event.type which will throw if event.event is undefined.
🛠️ Suggested approach for signature verification
import crypto from "crypto";
import { env } from "~/env.server";

function verifySlackSignature(request: Request, body: string): boolean {
  const timestamp = request.headers.get("X-Slack-Request-Timestamp");
  const signature = request.headers.get("X-Slack-Signature");
  
  if (!timestamp || !signature) return false;
  
  // Prevent replay attacks (5 min window)
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  
  const sigBaseString = `v0:${timestamp}:${body}`;
  const mySignature = "v0=" + crypto
    .createHmac("sha256", env.SLACK_SIGNING_SECRET)
    .update(sigBaseString)
    .digest("hex");
    
  return crypto.timingSafeEqual(
    Buffer.from(mySignature),
    Buffer.from(signature)
  );
}

Comment on lines +57 to +58
// Route message to OpenClaw container (on VPS)
const containerUrl = `http://178.128.150.129:${agent.containerPort}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Hardcoded IP address won't work across environments.

The container URL uses a hardcoded IP 178.128.150.129. This should be configured via environment variable.

+import { env } from "~/env.server";
+
-      const containerUrl = `http://178.128.150.129:${agent.containerPort}`;
+      const containerUrl = `http://${env.OPENCLAW_VPS_HOST}:${agent.containerPort}`;

Source: Coding guidelines

Comment on lines +80 to +89
await prisma.agentExecution.create({
data: {
agentId: agent.id,
message: text,
response: agentResponse,
executionTimeMs: 0, // TODO: Measure actual execution time
inputTokens: containerData?.inputTokens,
outputTokens: containerData?.outputTokens,
},
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Potential undefined value stored in database.

slackEvent.text (Line 28) could be undefined for certain Slack event types. Storing undefined in the required message field could cause a Prisma error or store unexpected values.

       await prisma.agentExecution.create({
         data: {
           agentId: agent.id,
-          message: text,
+          message: text ?? "",
           response: agentResponse,
           executionTimeMs: 0, // TODO: Measure actual execution time

Comment on lines +1 to +8
-- DropIndex
DROP INDEX "public"."SecretStore_key_idx";

-- DropIndex
DROP INDEX "public"."TaskRun_runtimeEnvironmentId_createdAt_idx";

-- DropIndex
DROP INDEX "public"."TaskRun_runtimeEnvironmentId_id_idx";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Remove extraneous Prisma-generated changes and use separate migration for existing table indexes.

Per coding guidelines:

  1. Lines 16-44 modify junction tables (_BackgroundWorkerToBackgroundWorkerFile, _BackgroundWorkerToTaskQueue, _TaskRunToTaskRunTag, _WaitpointRunConnections, _completedWaitpoints) - these should be removed as extraneous Prisma diff noise.
  2. Lines 1-8 drop indexes on existing tables (SecretStore, TaskRun) and Lines 105-112 recreate them - these should use CREATE INDEX CONCURRENTLY IF NOT EXISTS in a separate migration file to avoid table locks in production.

Based on coding guidelines: "Clean up generated Prisma migrations by removing extraneous lines for junction tables... When adding indexes to existing tables, use CREATE INDEX CONCURRENTLY IF NOT EXISTS... and place each concurrent index in its own separate migration file."

Also applies to: 16-44

Source: Coding guidelines

Comment on lines +3218 to +3222
/// Webhook token for Slack
slackWebhookToken String?

/// User's API keys (encrypted)
apiKeys Json?

Copy link
Copy Markdown
Contributor

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

Sensitive credentials stored as plain text.

slackWebhookToken (Line 3219) and apiKeys (Line 3222) store sensitive credentials. The codebase already has a SecretReference pattern (used for MFA secrets in the User model at Lines 52-53). Consider using SecretReference relations or an encrypted storage mechanism for these fields rather than storing them as plain String/Json.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 11 potential issues.

View 3 additional findings in Devin Review.

Open in Devin Review

invalidSelection
? "border-amber-800 bg-amber-950 text-amber-400"
: "border-blue-800 bg-[#1B2334] text-blue-400"
: "border-blue-800 bg-blue-50 text-blue-400"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Zoom tooltip background changed from dark to near-white, breaking dark theme

The zoom selection tooltip background was changed from bg-[#1B2334] (a dark navy blue matching the dark theme) to bg-blue-50 (Tailwind's default #eff6ff, an almost-white color). This applies to both the tooltip body (line 130) and its arrow indicator (line 144). In the dark-themed app, this renders as a bright white tooltip with light blue text (text-blue-400), making it extremely jarring and potentially unreadable against the dark chart background.

Suggested change
: "border-blue-800 bg-blue-50 text-blue-400"
: "border-blue-800 bg-[#1B2334] text-blue-400"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

invalidSelection
? "border-l border-t border-amber-800 bg-amber-950"
: "border-l border-t border-blue-800 bg-[#1B2334]"
: "border-l border-t border-blue-800 bg-blue-50"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Zoom tooltip arrow background also changed to near-white bg-blue-50

Same issue as the tooltip body — the arrow's background was changed from bg-[#1B2334] to bg-blue-50, creating a bright white arrow on a dark chart. This is the companion change to the tooltip body at line 130.

Suggested change
: "border-l border-t border-blue-800 bg-blue-50"
: "border-l border-t border-blue-800 bg-[#1B2334]"
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

{...props}
className={cn(
"overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 focus-custom",
"overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300 focus-custom",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 scrollbar-thumb-charcoal-600 replaced with scrollbar-thumb-gray-300, breaking dark theme scrollbars across entire app

All instances of scrollbar-thumb-charcoal-600 were globally replaced with scrollbar-thumb-gray-300. The custom charcoal-600 is defined as #3B3E45 (a dark gray matching the dark theme) in apps/webapp/tailwind.config.js:65. The gray color is NOT customized in the Tailwind config, so gray-300 resolves to Tailwind's default #D1D5DB — a very light gray. This makes all scrollbar thumbs extremely bright against the dark background across ~30 components (side menus, editors, popovers, tables, detail views, etc.).

Suggested change
"overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-300 focus-custom",
"overflow-y-auto overscroll-contain scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600 focus-custom",
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1 to +44
-- DropIndex
DROP INDEX "public"."SecretStore_key_idx";

-- DropIndex
DROP INDEX "public"."TaskRun_runtimeEnvironmentId_createdAt_idx";

-- DropIndex
DROP INDEX "public"."TaskRun_runtimeEnvironmentId_id_idx";

-- AlterTable
ALTER TABLE "public"."FeatureFlag" ALTER COLUMN "updatedAt" DROP DEFAULT;

-- AlterTable
ALTER TABLE "public"."IntegrationDeployment" ALTER COLUMN "updatedAt" DROP DEFAULT;

-- AlterTable
ALTER TABLE "public"."_BackgroundWorkerToBackgroundWorkerFile" ADD CONSTRAINT "_BackgroundWorkerToBackgroundWorkerFile_AB_pkey" PRIMARY KEY ("A", "B");

-- DropIndex
DROP INDEX "public"."_BackgroundWorkerToBackgroundWorkerFile_AB_unique";

-- AlterTable
ALTER TABLE "public"."_BackgroundWorkerToTaskQueue" ADD CONSTRAINT "_BackgroundWorkerToTaskQueue_AB_pkey" PRIMARY KEY ("A", "B");

-- DropIndex
DROP INDEX "public"."_BackgroundWorkerToTaskQueue_AB_unique";

-- AlterTable
ALTER TABLE "public"."_TaskRunToTaskRunTag" ADD CONSTRAINT "_TaskRunToTaskRunTag_AB_pkey" PRIMARY KEY ("A", "B");

-- DropIndex
DROP INDEX "public"."_TaskRunToTaskRunTag_AB_unique";

-- AlterTable
ALTER TABLE "public"."_WaitpointRunConnections" ADD CONSTRAINT "_WaitpointRunConnections_AB_pkey" PRIMARY KEY ("A", "B");

-- DropIndex
DROP INDEX "public"."_WaitpointRunConnections_AB_unique";

-- AlterTable
ALTER TABLE "public"."_completedWaitpoints" ADD CONSTRAINT "_completedWaitpoints_AB_pkey" PRIMARY KEY ("A", "B");

-- DropIndex
DROP INDEX "public"."_completedWaitpoints_AB_unique";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Migration includes extraneous destructive changes that violate repository rules

The migration file violates the explicit rules in internal-packages/database/CLAUDE.md:28-35, which state: "Clean up generated migration - remove extraneous lines for: _BackgroundWorkerToBackgroundWorkerFile, _BackgroundWorkerToTaskQueue, _TaskRunToTaskRunTag, _WaitpointRunConnections, _completedWaitpoints, SecretStore_key_idx, Various TaskRun indexes". The migration includes all of these auto-generated changes. Most critically, it drops and recreates indexes on the TaskRun table (lines 5-8, 109-112) without using CREATE INDEX CONCURRENTLY, violating the rule at internal-packages/database/CLAUDE.md:45 that indexes on existing tables must use CONCURRENTLY to avoid table locks in production. It also drops updatedAt defaults from FeatureFlag and IntegrationDeployment (lines 11-14) and modifies join table constraints (lines 17-44) — all extraneous changes unrelated to the OpenClaw agent feature.

Prompt for agents
The migration file contains many auto-generated extraneous changes that must be removed per the rules in internal-packages/database/CLAUDE.md. Specifically:

1. Remove all DROP INDEX and CREATE INDEX lines for SecretStore_key_idx and TaskRun indexes (lines 1-8 and 105-112) — these are auto-generated diffs that should not be in this migration.
2. Remove the ALTER TABLE lines for FeatureFlag and IntegrationDeployment updatedAt defaults (lines 11-14).
3. Remove all the join table constraint changes for _BackgroundWorkerToBackgroundWorkerFile, _BackgroundWorkerToTaskQueue, _TaskRunToTaskRunTag, _WaitpointRunConnections, and _completedWaitpoints (lines 17-44).

The migration should only contain the CREATE TABLE statements for AgentConfig, AgentExecution, AgentHealthCheck, their indexes (which are on newly created tables so CONCURRENTLY is not needed), and the AddForeignKey statements. Re-run pnpm run db:migrate:dev:create to regenerate a clean migration.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +24 to +25
const agentConfig = await prisma.agentConfig.findUnique({
where: { id: agentId },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Uses findUnique instead of findFirst, violating Prisma query pattern rules

The apps/webapp/CLAUDE.md:126 explicitly states: "Always use findFirst instead of findUnique." The rationale is that Prisma's findUnique has an implicit DataLoader with known bugs (uppercase UUIDs returning null, composite key SQL issues, performance problems). The new file uses prisma.agentConfig.findUnique() at line 24. The same violation also occurs in apps/webapp/app/routes/agents.$agentId.status.tsx:28.

Suggested change
const agentConfig = await prisma.agentConfig.findUnique({
where: { id: agentId },
const agentConfig = await prisma.agentConfig.findFirst({
where: { id: agentId },
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +33 to +40
const lastAgent = await prisma.agentConfig.findFirst({
where: {
containerPort: { not: null },
},
orderBy: { containerPort: "desc" },
});

const nextPort = (lastAgent?.containerPort || 8000) + 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Port allocation race condition — concurrent provisions can assign the same port

In api.agents.provision.ts:33-40, the port allocation reads the highest existing containerPort and increments it, then writes the result. This is a classic TOCTOU (time-of-check-to-time-of-use) race: two concurrent provisioning requests can read the same max port and both compute the same nextPort, assigning duplicate ports to different agents. There is no database uniqueness constraint on containerPort in the schema (internal-packages/database/prisma/schema.prisma:3209) and no transaction or locking around this read-then-write sequence.

Prompt for agents
The port allocation in api.agents.provision.ts has a TOCTOU race condition. The code reads the max containerPort, increments it, and writes it in separate operations without any locking. Two concurrent requests can get the same port.

To fix this, either:
1. Add a unique constraint on containerPort in the Prisma schema and handle the unique violation error with a retry loop.
2. Use a Prisma interactive transaction ($transaction) with serializable isolation to atomically read the max port and write the new one.
3. Use a database sequence or advisory lock for port allocation.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +71 to +75
const provisionResponse = await fetch("http://localhost:3000/api/agents/provision", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ agentId: agentConfig.id }),
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Provisioning fetch hardcoded to localhost:3000 — wrong port, will always fail

In agents.setup.tsx:71, the action handler calls fetch("http://localhost:3000/api/agents/provision", ...) to trigger provisioning. However, the webapp runs on port 3030 locally (per CONTRIBUTING.md and CLAUDE.md), and in production the URL would be entirely different. This means the provisioning request will always fail to connect. The error is silently caught at line 83, so the agent is created and the user is redirected to the status page, but the agent will never actually be provisioned.

Prompt for agents
The provisioning fetch in agents.setup.tsx hardcodes http://localhost:3000 which is wrong — the webapp runs on port 3030 locally and a completely different URL in production. Instead of making an HTTP request to the same server, call the provisioning logic directly as a function import, or use the APP_ORIGIN / LOGIN_ORIGIN environment variable from env.server.ts to construct the correct base URL. Making an HTTP call from a server action to the same server is also an anti-pattern — prefer calling the provisioning service code directly.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +4 to +17
width="32"
height="32"
viewBox="0 0 32 32"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<rect width="32" height="32" rx="8" fill="#2E7D32" />
<path
fillRule="evenodd"
clipRule="evenodd"
d="M96.1017 113.4L160.679 4.57764e-05L320.718 281.045H0.638916L65.2159 167.642L110.896 194.382L92.0035 227.561H229.354L160.679 106.965L141.786 140.144L96.1017 113.4Z"
fill="url(#paint0_linear_465_1663)"
d="M16 6L24 24H8L16 6Z"
fill="white"
fillOpacity="0.9"
/>
<defs>
<linearGradient
id="paint0_linear_465_1663"
x1="320.718"
y1="140.687"
x2="0.638918"
y2="140.687"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#41FF54" />
<stop offset="1" stopColor="#E7FF52" />
</linearGradient>
</defs>
<circle cx="16" cy="20" r="2" fill="#2E7D32" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 LogoIcon and LogoType completely replaced with different brand identity

The Trigger.dev logo SVGs in LogoIcon.tsx and LogoType.tsx have been entirely replaced. The original logo was a detailed geometric shape with a green-to-yellow gradient (#41FF54 to #E7FF52), consistent with Trigger.dev's established brand. The new logo is a green rounded rectangle (#2E7D32) with a simple white triangle and dot — a completely different design language. The LogoType also replaces the detailed path-based wordmark with an SVG <text> element rendering "Trigger.dev", which may render differently across browsers depending on font availability (Geist Variable, Inter, Helvetica Neue, sans-serif). This is a significant brand change that should be verified as intentional.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +11 to +14
export const action = async ({ request }: ActionFunctionArgs) => {
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, { status: 405 });
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 New agent routes bypass the standard dashboardAction/dashboardLoader authorization patterns

The existing webapp routes consistently use dashboardAction/dashboardLoader wrappers (e.g., apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.bulk-actions.$bulkActionParam/route.tsx:101) which handle authorization checks with { action: 'write', resource: { type: '...' } }. The new agent routes (agents.setup.tsx, agents.$agentId.status.tsx, api.agents.provision.ts, webhooks.slack.ts) don't use these patterns. The setup and status routes use requireUser for authentication but skip the authorization layer entirely. The provisioning endpoint has no authentication at all. This is inconsistent with the established patterns in the codebase.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +10 to +20
export const action = async ({ request }: ActionFunctionArgs) => {
if (request.method !== "POST") {
return json({ error: "Method not allowed" }, { status: 405 });
}

try {
const event = await request.json() as any;

// Handle Slack URL verification
if (event.type === "url_verification") {
return json({ challenge: event.challenge });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚩 Webhook route processes Slack events without verifying request signature

The webhooks.slack.ts route handles incoming Slack events including url_verification and event_callback message types. Slack's documentation requires verifying the X-Slack-Signature header using the signing secret to ensure requests actually come from Slack. While this is a security concern (handled separately), it's worth noting from a correctness standpoint that the url_verification challenge response at line 19 would allow anyone to register this endpoint as a Slack webhook URL, and the message routing at lines 24+ would process any POST body that matches the expected shape, potentially triggering agent executions and database writes from non-Slack sources.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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