diff --git a/backend/Dockerfile.worker b/backend/Dockerfile.worker
index a8a278287..ea0a13634 100644
--- a/backend/Dockerfile.worker
+++ b/backend/Dockerfile.worker
@@ -38,9 +38,14 @@ RUN dotnet restore CodeSpace.sln
RUN dotnet publish src/CodeSpace.Api/CodeSpace.Api.csproj -c Release -o /app --no-restore
# ── 2. The Node-based harness CLIs, version-pinned (override at build time with --build-arg) ──
+# These two ARG defaults are the SINGLE SOURCE OF TRUTH for the pinned CLI versions. A test
+# (HarnessVersionPinTests) asserts CodexHarness.DefaultVersion / ClaudeCodeHarness.DefaultVersion match these exact
+# strings, so the harness-reported version can NEVER silently drift from what the worker actually installs — bump
+# here and the test fails until the C# constants follow. `deploy/sync-local-harnesses.sh` installs these same pins
+# locally, so a dev box matches the worker. Keep all three (this file · the harness consts · a local install) in lockstep.
FROM node:20-bookworm-slim AS agent-cli
ARG CODEX_CLI_VERSION=0.142.2
-ARG CLAUDE_CODE_VERSION=2.1.191
+ARG CLAUDE_CODE_VERSION=2.1.193
RUN npm install -g "@openai/codex@${CODEX_CLI_VERSION}" "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}"
# ── 3. Runtime: agent-execution + isolation deps + the Node runtime & CLIs + the published app ──
diff --git a/backend/deploy/sync-local-harnesses.sh b/backend/deploy/sync-local-harnesses.sh
new file mode 100755
index 000000000..128e8ef41
--- /dev/null
+++ b/backend/deploy/sync-local-harnesses.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# Sync the LOCAL agent-CLI harnesses to the EXACT versions the worker image pins — so a dev box runs the same codex
+# /claude the worker does, and the harness-reported version (CodexHarness/ClaudeCodeHarness.DefaultVersion, pinned to
+# these same ARGs by HarnessVersionPinTests) stays honest. The single source of truth is backend/Dockerfile.worker.
+#
+# backend/deploy/sync-local-harnesses.sh
+#
+# After a version bump, the lockstep is: edit the Dockerfile ARG → update the matching DefaultVersion C# const (the
+# pin test enforces this) → run this script on every dev box. Nothing to remember beyond "bump the ARG, run this".
+set -euo pipefail
+
+DOCKERFILE="$(cd "$(dirname "$0")/.." && pwd)/Dockerfile.worker"
+[ -f "$DOCKERFILE" ] || { echo "✗ $DOCKERFILE not found"; exit 1; }
+
+codex_v=$(grep -oE 'ARG CODEX_CLI_VERSION=[^[:space:]]+' "$DOCKERFILE" | head -1 | cut -d= -f2)
+claude_v=$(grep -oE 'ARG CLAUDE_CODE_VERSION=[^[:space:]]+' "$DOCKERFILE" | head -1 | cut -d= -f2)
+echo "Worker-pinned versions: codex=$codex_v claude=$claude_v"
+
+npm install -g "@openai/codex@${codex_v}" "@anthropic-ai/claude-code@${claude_v}"
+
+# Verify the EFFECTIVE (PATH) binaries match — a native claude/codex install can shadow the npm one.
+check() { # $1=binary $2=expected
+ local got; got=$("$1" --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "missing")
+ if [ "$got" = "$2" ]; then echo "✓ $1 $got"; else
+ echo "⚠ $1 on PATH is $got, expected $2 — a native install may be shadowing npm. Update it (e.g. 'claude update') or fix PATH order."
+ fi
+}
+check codex "$codex_v"
+check claude "$claude_v"
diff --git a/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Claude/ClaudeCodeHarness.cs b/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Claude/ClaudeCodeHarness.cs
index abedd2149..bba7f1565 100644
--- a/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Claude/ClaudeCodeHarness.cs
+++ b/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Claude/ClaudeCodeHarness.cs
@@ -76,7 +76,8 @@ public sealed class ClaudeCodeHarness : IAgentHarness, IModelCredentialProjector
private const string AnthropicProvider = "Anthropic";
- private const string DefaultVersion = "2.1.0";
+ /// The pinned Claude Code CLI version — MUST match CLAUDE_CODE_VERSION in backend/Dockerfile.worker (the single source of truth); a pin test fails if they drift.
+ internal const string DefaultVersion = "2.1.193";
private const string DefaultCommand = "claude";
diff --git a/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Codex/CodexHarness.cs b/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Codex/CodexHarness.cs
index 0fa8bd00f..6fcd2f7eb 100644
--- a/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Codex/CodexHarness.cs
+++ b/backend/src/CodeSpace.Core/Services/Agents/Harnesses/Codex/CodexHarness.cs
@@ -49,7 +49,8 @@ public sealed class CodexHarness : IAgentHarness, IModelCredentialProjector, IMc
///
public const string ConfigHomeEnvVar = "CODEX_HOME";
- private const string DefaultVersion = "0.2.0";
+ /// The pinned Codex CLI version — MUST match CODEX_CLI_VERSION in backend/Dockerfile.worker (the single source of truth); a pin test fails if they drift.
+ internal const string DefaultVersion = "0.142.2";
private const string DefaultCommand = "codex";
diff --git a/backend/tests/CodeSpace.UnitTests/Workflows/CodexHarnessTests.cs b/backend/tests/CodeSpace.UnitTests/Workflows/CodexHarnessTests.cs
index 47d00dc30..f0373798e 100644
--- a/backend/tests/CodeSpace.UnitTests/Workflows/CodexHarnessTests.cs
+++ b/backend/tests/CodeSpace.UnitTests/Workflows/CodexHarnessTests.cs
@@ -304,7 +304,7 @@ public void Version_uses_the_default_then_the_env_override()
try
{
System.Environment.SetEnvironmentVariable(CodexHarness.VersionEnvVar, null);
- new CodexHarness().Version.ShouldBe("0.2.0");
+ new CodexHarness().Version.ShouldBe(CodexHarness.DefaultVersion); // tracks the Dockerfile pin (HarnessVersionPinTests)
System.Environment.SetEnvironmentVariable(CodexHarness.VersionEnvVar, "9.9.9");
new CodexHarness().Version.ShouldBe("9.9.9");
diff --git a/backend/tests/CodeSpace.UnitTests/Workflows/HarnessVersionPinTests.cs b/backend/tests/CodeSpace.UnitTests/Workflows/HarnessVersionPinTests.cs
new file mode 100644
index 000000000..1c98be67e
--- /dev/null
+++ b/backend/tests/CodeSpace.UnitTests/Workflows/HarnessVersionPinTests.cs
@@ -0,0 +1,47 @@
+using System;
+using System.IO;
+using System.Text.RegularExpressions;
+using CodeSpace.Core.Services.Agents.Harnesses.Claude;
+using CodeSpace.Core.Services.Agents.Harnesses.Codex;
+using Shouldly;
+
+namespace CodeSpace.UnitTests.Workflows;
+
+///
+/// Pins each harness's DefaultVersion constant to the SINGLE SOURCE OF TRUTH — the
+/// CODEX_CLI_VERSION / CLAUDE_CODE_VERSION ARG in backend/Dockerfile.worker (the version the
+/// worker image actually installs). A bump in the Dockerfile that isn't mirrored into the C# constant (or vice
+/// versa) FAILS here, so the harness-reported version can never silently drift from what the worker runs. The third
+/// surface — a developer's local install — is synced from the same ARG by deploy/sync-local-harnesses.sh.
+///
+[Trait("Category", "Unit")]
+public class HarnessVersionPinTests
+{
+ [Fact]
+ public void Codex_default_version_matches_the_worker_dockerfile_pin() =>
+ DockerfileArg("CODEX_CLI_VERSION").ShouldBe(CodexHarness.DefaultVersion);
+
+ [Fact]
+ public void Claude_default_version_matches_the_worker_dockerfile_pin() =>
+ DockerfileArg("CLAUDE_CODE_VERSION").ShouldBe(ClaudeCodeHarness.DefaultVersion);
+
+ private static string DockerfileArg(string name)
+ {
+ var content = File.ReadAllText(LocateWorkerDockerfile());
+ var match = Regex.Match(content, $@"ARG\s+{Regex.Escape(name)}=(\S+)");
+
+ match.Success.ShouldBeTrue($"ARG {name} not found in backend/Dockerfile.worker");
+ return match.Groups[1].Value;
+ }
+
+ private static string LocateWorkerDockerfile()
+ {
+ for (var dir = new DirectoryInfo(AppContext.BaseDirectory); dir is not null; dir = dir.Parent)
+ {
+ var candidate = Path.Combine(dir.FullName, "backend", "Dockerfile.worker");
+ if (File.Exists(candidate)) return candidate;
+ }
+
+ throw new FileNotFoundException("backend/Dockerfile.worker not found walking up from " + AppContext.BaseDirectory);
+ }
+}