Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/webapp/app/components/integrations/VercelOnboardingModal.tsx`:
- Around line 1083-1085: The copy in VercelOnboardingModal (the Paragraph
elements that currently state "This allows automatic deployments and build
synchronization" and the nearby sentence at lines cited) overstates the Vercel
integration; update the text in the Paragraph(s) inside the
VercelOnboardingModal component to say that the integration links deployment
records and context between AirTrigger and Vercel, and that builds and git
metadata are handled by the GitHub app, removing any mention of automatic
deployments or build synchronization; keep phrasing concise and user-facing so
it clarifies responsibilities (Vercel = deployments/context linking, GitHub app
= builds/git metadata).
In `@apps/webapp/app/features.server.ts`:
- Around line 7-13: The isManagedCloud function currently reads
process.env.CLOUD_ENV directly; update the file to import the env export from
env.server.ts (e.g., import { env } from "env.server" or your repo’s env module)
and replace process.env.CLOUD_ENV with env.CLOUD_ENV in the isManagedCloud
return expression so all environment access uses the env export; ensure the
import name matches the exported symbol and adjust any references accordingly.
In `@apps/webapp/app/routes/agents.setup.tsx`:
- Around line 69-75: The code is calling the app via a hard-coded
"http://localhost:3000" when POSTing { agentId: agentConfig.id } to
"/api/agents/provision" (provisionResponse/fetch), which breaks in preview/prod
or behind proxies; instead, move the provisioning logic out of the HTTP handler
into a shared server function (e.g., export a server-side
provisionAgent(agentId) helper) and invoke that helper directly from
agents.setup.tsx on the server, or derive the base URL from the incoming
request/environment and use that (e.g., use request.url or an env var) so you
don't hard-code localhost. Ensure you call the shared function with
agentConfig.id rather than issuing a local HTTP fetch to localhost.
- Around line 221-229: The placeholder for the Slack webhook input is misleading
(shows a bot token like "xoxb-...") but the value is later used to build an
incoming webhook URL in webhooks.slack.ts; update the input with id/name
slackWebhookToken (and its label) to use a webhook token/path placeholder such
as "T0XXXXXXXX/B0XXXXXXXX/XXXXXXXXXXXXXXXXXXXX" or the full path segment format
so callers know to paste the incoming webhook token (the part appended to
https://hooks.slack.com/services/) rather than a bot token.
In `@apps/webapp/app/routes/api.agents.provision.ts`:
- Around line 32-62: The current port allocation reads the max containerPort
(prisma.agentConfig.findFirst) and then updates the agent
(prisma.agentConfig.update) which is racy; change to an atomic allocation: add a
DB uniqueness constraint/index on containerPort and perform the allocation
inside a transaction (or use a DB sequence/raw SQL SELECT MAX(...) FOR UPDATE)
to compute nextPort and immediately persist it in the same transaction when
creating/updating the agent record, and handle unique-constraint violations by
retrying the allocation loop a few times before failing; update the code paths
using nextPort, containerName, and the agentConfig update to use this
transactional/atomic allocation flow.
- Around line 22-31: The provisioning route currently reads and updates agent
configs by agentId only (prisma.agentConfig.findUnique and the provisioning
update path), allowing any caller to provision others' agents; require and
validate an authenticated/internal caller (e.g., via a verified session or
internal token), extract the caller's ownerId (or teamId), and scope both reads
and writes to that owner: change the read to a scoped lookup (e.g., findFirst /
findUnique with where: { id: agentId, ownerId: currentOwnerId }) or use
updateMany for the write with where: { id: agentId, ownerId: currentOwnerId } so
the update only applies if ownership matches, and return 404 if no matching
record; ensure the same ownership check is applied in the update path referenced
around lines 55-62.
- Around line 16-20: Move the call to request.json() inside the handler's try
block and replace the type assertion with a zod validation: create a zod schema
like z.object({ agentId: z.string().min(1) }), parse the body using schema.parse
or safeParse (e.g., schema.safeParse(body)), and only destructure/use agentId
after validation; on validation failure return json({ error: "...validation
message..." }, { status: 400 }) so malformed or missing agentId returns a 400
instead of a 500. Ensure you reference the request.json() result for validation
and remove the unsafe "as { agentId: string }" assertion.
In `@apps/webapp/app/routes/webhooks.slack.ts`:
- Around line 39-45: The Slack agent lookup using prisma.agentConfig.findFirst({
where: { slackWorkspaceId, messagingPlatform: "slack", status: "healthy" } }) is
non-deterministic when multiple healthy agents exist; make routing deterministic
by adding an explicit ordering or tie-breaker to the query (e.g., orderBy a
stable column like id or createdAt) or by selecting a specific agent identifier
criterion, so update the query in webhooks.slack.ts (the
prisma.agentConfig.findFirst call) to include an orderBy (or otherwise
deterministically pick from findMany) to ensure consistent routing.
- Around line 31-36: The current logger.info call in webhooks.slack.ts logs the
Slack message body (text?.substring(0, 100)); change this to avoid logging
message content: keep workspaceId, channel, userId, and replace the text field
with a non-sensitive metric such as messageLength (e.g., text ? text.length : 0)
or a boolean hasText flag; update the logger.info invocation where workspaceId,
channel, userId, and text are used to instead emit messageLength/hasText to
prevent PII/secrets exposure.
- Around line 60-77: Add an AbortController-based timeout to the fetch call (use
AbortController/AbortSignal and setTimeout to abort after X ms) and check
containerResponse.ok immediately after fetch; if !ok, log the status and throw
or return so you don't call containerResponse.json() or proceed. Only parse
containerResponse.json() and derive agentResponse when containerResponse.ok is
true, otherwise skip the subsequent execution recording and Slack reply logic
(the variables containerResponse, containerResponse.json, and agentResponse are
the key places to update).
- Around line 23-30: Add a guard at the start of the Slack message handler in
webhooks.slack.ts to skip processing bot/postback messages and messages created
by our own bot to prevent recursion: check slackEvent.subtype and
slackEvent.bot_id and return early for values like "bot_message" or any
non-empty subtype, and also compare slackEvent.user (userId) against the app's
bot user id (from config/ENV or stored workspace bot id) and return if they
match; keep this check before using workspaceId/channel/text/userId and before
the code that posts replies (the response posting block around the current reply
logic) so self-generated replies are not re-processed.
- Around line 15-20: This webhook currently parses request.json() and trusts the
payload; instead first read the raw request body and verify the Slack signature
using X-Slack-Request-Signature and X-Slack-Request-Timestamp before any JSON
parsing or action, rejecting requests with invalid or stale signatures; after
successful verification, parse the JSON and validate the event shape with zod
(replace the unsafe "as any" usage around request.json()) and ensure handlers
that trigger container calls (the code path around the call at line ~61), mark
agents unhealthy (around line ~128), or write to the DB (lines ~80–89 and
~130–136) only run for validated, non-bot events—explicitly filter out events
that are from bots or the app itself to prevent recursion.
In
`@internal-packages/database/prisma/migrations/20260325122458_add_openclaw_agents/migration.sql`:
- Around line 1-44: The migration includes unrelated DDL that alters existing
objects; remove all non-agent changes by deleting the DROP INDEX statements for
"SecretStore_key_idx", "TaskRun_runtimeEnvironmentId_createdAt_idx",
"TaskRun_runtimeEnvironmentId_id_idx", the ALTER TABLE ... DROP DEFAULT lines
for "FeatureFlag"."updatedAt" and "IntegrationDeployment"."updatedAt", and the
ALTER/ADD CONSTRAINT and DROP INDEX blocks for junction tables
"_BackgroundWorkerToBackgroundWorkerFile", "_BackgroundWorkerToTaskQueue",
"_TaskRunToTaskRunTag", "_WaitpointRunConnections", and "_completedWaitpoints"
(i.e., any lines creating primary keys or dropping the corresponding
_*_AB_unique indexes). Keep only the three new agent table CREATE statements
plus their indexes and foreign keys; ensure no changes remain to SecretStore,
TaskRun, FeatureFlag, IntegrationDeployment, or the listed junction tables (also
remove the equivalent lines around 105-112).
In `@packages/cli-v3/src/consts.ts`:
- Around line 1-4: Remove or verify the unused CONFIG_FILES constant (export
const CONFIG_FILES) — if it serves no purpose delete it; then update all
c12.loadConfig / watchConfig calls (the loadConfig/name usages) to use name:
"airtrigger" instead of "trigger" (look for functions calling c12.loadConfig and
watchConfig), replace hardcoded "trigger.config.ts" occurrences (e.g., init
command that creates the config and build helpers referenced in
packageModules.ts and bundle.ts) with "airtrigger.config.ts", and finally run
the changeset step (pnpm run changeset:add) to record the rebranding change.
---
Outside diff comments:
In `@apps/webapp/app/components/navigation/HelpAndFeedbackPopover.tsx`:
- Around line 115-153: The help menu uses SideMenuItem entries with placeholder
links (to="#" plus target="_blank") which open empty tabs; update the
SideMenuItem instances (e.g., the ones named "Documentation", "Status", "Suggest
a feature", "Changelog", "Discord", "Book a 15 min call") to either (A) remove
the menu items entirely, (B) replace the placeholder hrefs with the real
AirTrigger URLs, or (C) make them non-navigable disabled items by removing
target="_blank", setting to={undefined} or to="#" without target, adding
aria-disabled and a visual "disabled" style/tooltip; pick one approach and apply
consistently to all listed SideMenuItem entries so they no longer open blank
tabs.
In `@apps/webapp/app/services/apiAuth.server.ts`:
- Around line 648-656: Update the mismatched issuer/audience constants and the
token validation error handling: change JWT_ISSUER and JWT_AUDIENCE in
packages/core/src/v3/jwt.ts to "https://id.airtrigger.dev" and
"https://api.airtrigger.dev" respectively so tokens issued by the SDK match the
webapp, and in the webapp's validateJWTTokenAndRenew function add handling for
JWTClaimValidationFailed (or implement dual-validation logic inside the same
function to accept both the old and new issuer/audience during migration) so
claim validation failures are caught and handled the same way as JWTExpired.
---
Minor comments:
In `@apps/webapp/app/components/runs/v3/ReplayRunDialog.tsx`:
- Line 242: In ReplayRunDialog locate the editor container div whose className
includes the token "rounded-smbg-charcoal-900" and split that combined token
into two valid Tailwind classes by replacing it with "rounded-sm
bg-charcoal-900" (preserving the rest of the className string and spacing);
update the className in the div inside the ReplayRunDialog component so rounding
and background styles apply correctly.
In `@apps/webapp/app/root.tsx`:
- Around line 40-46: The robots meta currently uses typeof window which is true
during SSR and causes incorrect "noindex" output; instead compute a boolean like
shouldIndex in the route loader (use server-side config/env such as
env.APP_ORIGIN or a dedicated env var to determine if origin is
cloud.airtrigger.dev) and return it as part of loader data, then read that
loader data in the root component and set the meta entry (name: "robots",
content: shouldIndex ? "index, follow" : "noindex, nofollow"). Update the loader
function and the meta-generation logic to reference the loader-provided flag
(e.g., loader returns { shouldIndex } and meta reads loaderData.shouldIndex) so
the decision is correct during SSR.
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam._index/route.tsx:
- Around line 776-786: The DialogTitle "AirTrigger walkthrough" is inconsistent
with the embedded YouTube video id "YH_4c0K7fGM" in the iframe (which appears to
be the original Trigger.dev walkthrough); update the UI to match branding by
either replacing the iframe src with the AirTrigger-branded video URL when
available, or change the DialogTitle/text to indicate this is the Trigger.dev
walkthrough (or add a short note below DialogTitle), making the change around
the DialogTitle component and the iframe src attribute to ensure the title and
embedded video content are consistent.
In `@apps/webapp/app/routes/agents`.$agentId.status.tsx:
- Line 178: TableCell currently renders check.responseTimeMs directly which can
be null/undefined and yields "nullms"/"undefinedms"; update the render inside
the TableCell that displays check.responseTimeMs to guard for missing values
(e.g., if check.responseTimeMs is null/undefined show a placeholder like "—" or
"N/A", otherwise render the value with "ms"). Locate the TableCell that
references check.responseTimeMs and replace the direct interpolation with a
conditional expression that outputs a human-friendly placeholder when
responseTimeMs is absent.
In
`@apps/webapp/app/routes/api.v1.orgs`.$organizationSlug.projects.$projectParam.vercel.projects.ts:
- Line 17: Update the endpoint documentation string that currently reads "API
endpoint to retrieve connected Vercel projects for a AirTrigger project." to use
the correct article "an" — change it to "API endpoint to retrieve connected
Vercel projects for an AirTrigger project." Locate and edit the
docstring/comment at the top of
apps/webapp/app/routes/api.v1.orgs.$organizationSlug.projects.$projectParam.vercel.projects.ts
(the line containing the current sentence) and replace "a AirTrigger" with "an
AirTrigger".
In `@apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts`:
- Around line 100-101: Update the JSDoc comment that reads "Maps a AirTrigger
environment type to its Vercel target identifier(s)." to use the correct
indefinite article: change "a AirTrigger" to "an AirTrigger" in the comment
above the mapping for VerceI project integration
(vercelProjectIntegrationSchema.ts) so the doc reads "Maps an AirTrigger
environment type to its Vercel target identifier(s)."
In `@internal-packages/emails/emails/components/Footer.tsx`:
- Line 10: In the Footer component (Footer.tsx) update the copyright string that
currently renders "©AirTrigger, 1111B S Governors Ave STE 6433, Dover, DE 19904
|{" "}" to include a space after the © symbol (i.e., change "©AirTrigger" to "©
AirTrigger") so the displayed text follows standard formatting; locate the
literal in the JSX and insert the space directly in that string.
---
Nitpick comments:
In
`@apps/webapp/app/routes/_app.orgs`.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx:
- Line 940: Summary: PR description mentions agent/webhook and Docker features
but this diff only changes a scrollbar thumb color (className "min-w-[20rem]
overflow-y-auto p-2 scrollbar-thin scrollbar-track-transparent
scrollbar-thumb-gray-300"); either update the PR description to reflect
UI/rebranding work or remove these styling edits from this feature PR. Fix:
locate the JSX using the className string above (and the duplicate occurrence of
the same class elsewhere) and either (a) move the styling changes into a
separate branch/PR dedicated to rebranding/visual tweaks and revert them here,
or (b) amend the PR title/description to include the rebranding/visual changes
so the PR accurately represents the changes.
In `@apps/webapp/app/routes/agents`.$agentId.status.tsx:
- Around line 28-44: The prisma lookup currently fetches agentConfig by id then
checks ownership afterward; change the query in prisma.agentConfig.findUnique to
include the signed-in userId in the where clause (e.g., where: { id: agentId,
userId: user.id }) so the DB enforces the authorization boundary while keeping
the existing includes (executions, healthChecks) and ordering/take options.
In `@apps/webapp/app/services/mfa/multiFactorAuthentication.server.ts`:
- Line 139: Update the TOTP issuer label from "airtrigger" to the proper-cased
"AirTrigger" wherever the OTP URL is generated: change the call that builds the
auth URL (createOTP(secret).url("airtrigger", user.email)) to use "AirTrigger"
instead; ensure the same update is applied to both occurrences (the
createOTP(...).url(...) invocation at the site currently using "airtrigger" and
the other identical call later in the file).
In `@apps/webapp/app/v3/marqs/index.server.ts`:
- Line 2624: Update the error string that currently mentions
"process.env.REDIS_HOST and process.env.REDIS_PORT" to reference the env
abstraction instead (use env.REDIS_HOST and env.REDIS_PORT) so the message
aligns with the code that checks env.REDIS_HOST / env.REDIS_PORT; locate the
AirTrigger initialization (symbol: AirTrigger) in index.server.ts and replace
the literal process.env wording in the thrown error with a message that mentions
env.REDIS_HOST and env.REDIS_PORT (or a neutral phrase like "required
environment variables") to follow the project's env.server.ts access guideline.