From f6e121f8161a77f532cda471f96955a21a903884 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 22:13:58 +0000 Subject: [PATCH] fix: validate config_update_string with getInvalidConfigReason and add polling correlation check Co-Authored-By: mantra --- .../latest/internal/config/github/apply/route.tsx | 12 ++++++++++-- apps/dashboard/src/lib/config-update.tsx | 7 +++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/apps/backend/src/app/api/latest/internal/config/github/apply/route.tsx b/apps/backend/src/app/api/latest/internal/config/github/apply/route.tsx index 5d9782754..33f54ddf6 100644 --- a/apps/backend/src/app/api/latest/internal/config/github/apply/route.tsx +++ b/apps/backend/src/app/api/latest/internal/config/github/apply/route.tsx @@ -10,6 +10,7 @@ import { applyConfigUpdate, type GithubRepoRef } from "@/lib/config/repo-agent"; import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; import { runAsynchronouslyAndWaitUntil } from "@/utils/background-tasks"; import type { EnvironmentConfigOverrideOverride } from "@hexclave/shared/dist/config/schema"; +import { getInvalidConfigReason } from "@hexclave/shared/dist/config/format"; import { adaptSchema, adminAuthTypeSchema, yupNumber, yupObject, yupString } from "@hexclave/shared/dist/schema-fields"; import { StatusError, captureError } from "@hexclave/shared/dist/utils/errors"; @@ -68,8 +69,15 @@ export const POST = createSmartRouteHandler({ let configUpdate: EnvironmentConfigOverrideOverride; try { - configUpdate = JSON.parse(req.body.config_update_string); - } catch { + const parsed: unknown = JSON.parse(req.body.config_update_string); + const reason = getInvalidConfigReason(parsed, { configName: "config_update_string" }); + if (reason) { + throw new StatusError(StatusError.BadRequest, reason); + } + // Safe after getInvalidConfigReason confirms it's a valid config object + configUpdate = parsed as EnvironmentConfigOverrideOverride; + } catch (e) { + if (e instanceof StatusError) throw e; throw new StatusError(StatusError.BadRequest, "config_update_string is not valid JSON."); } diff --git a/apps/dashboard/src/lib/config-update.tsx b/apps/dashboard/src/lib/config-update.tsx index 1c5d357ea..249495656 100644 --- a/apps/dashboard/src/lib/config-update.tsx +++ b/apps/dashboard/src/lib/config-update.tsx @@ -523,6 +523,7 @@ function GithubPushBody({ // The run is now in flight. Lock the dialog (non-dismissible; Cancel aborts) // and flag the run as managed by this tab so the page-load watcher stays out // of the way until we settle. + const runStartedAt = Date.now(); dialogContext?.setGithubRunActive(true); onRunPhaseChange("running"); setActivity(null); @@ -541,8 +542,10 @@ function GithubPushBody({ continue; // transient — keep polling } const run = latest?.type === "pushed-from-github" ? latest.agent_run : null; - if (run == null || run.status === "running") { - if (typeof run?.progress === "string") setActivity(run.progress); + // Ignore stale agent_run entries from a previous run (or read-replica lag). + // The run we just started must have started_at >= our request timestamp. + if (run == null || run.status === "running" || (typeof run.started_at === "number" && run.started_at < runStartedAt - 5000)) { + if (run != null && run.status === "running" && typeof run?.progress === "string") setActivity(run.progress); continue; }