Skip to content

Commit 3aa2db3

Browse files
bai-uipathclaude
andauthored
fix(criteria): search the whole command in command_executed and bump harness SDKs for Opus 5.5 (#196)
* fix(criteria): search the whole command in command_executed, not just the first 2000 chars The ReDoS bound truncated every command to its first 2000 characters, so a checked command at the end of a long heredoc script was scored as never run. Search the rest of the command in bounded, line-aligned windows instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): bump harness SDKs and CLIs to latest - claude-agent-sdk 0.2.124 -> 0.2.159 (bundles Claude Code 2.1.281) and the image's Claude Code 2.1.177 -> 2.1.281, so the CLI the SDK runs and the one reported in environment_info agree. 2.1.281 knows Opus 5.5 and the current Sonnet 5 price, so claude-code turn costs match list pricing again. - openai-codex / openai-codex-cli-bin 0.144.4 -> 0.156.1 - google-antigravity 0.1.8 -> 0.1.18: workspace containment moved into localharness, so policy.workspace_only() no longer carries a path predicate; the test now checks the resolved workspaces cover the skill roots. - Pi 0.84.4 -> 0.87.1, anthropic 1.0.0 -> 1.8.0, litellm 1.98.0 -> 1.102.1, harbor 0.22.0 -> 0.23.0, uipath 2.10.31 -> 2.14.25 - Ignore pydantic's ReadOnly TypedDict warning from litellm>=1.102 types, and regenerate the codex golden for the two null fields the new SDK serializes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(harbor): declare SUPPORTS_ATIF as a ClassVar to match harbor 0.23 harbor 0.23.0 annotates BaseAgent.SUPPORTS_ATIF as ClassVar[bool], so the plain bool override fails pyright's reportIncompatibleVariableOverride. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): hold the SDK bump to releases past CI's 48h package-age gate CI's safe-chain rejects any package published less than 48 hours ago, so every Linux job failed at install. Relocked with every package cut off at 2026-09-21T23:00Z: claude-agent-sdk 0.2.157 (bundles Claude Code 2.1.277), anthropic 1.7.0, openai-codex and cli-bin 0.155.1, google-antigravity 0.1.17, litellm 1.102.0, uipath 2.14.23; Claude Code image ARG 2.1.277 and Pi 0.87.0. Claude Code 2.1.277 prices Sonnet 5 at list but has no Opus 5.5 entry, so Opus 5.5 runs still cost at its $5/$25 fallback until the next bump to 2.1.280 or later. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): exempt claude-agent-sdk from the 48h package-age gate and take the latest SDK CI's safe-chain holds back any package younger than 48h. claude-agent-sdk bundles the Claude Code CLI, and new-model support (Opus 5.5 pricing landed in 2.1.280) ships there faster than that window, so it joins openai-codex in the exclusion list. The repo secret SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS is set and replaces the literal fallback, so the entry is appended outside the `||` in pr-checks, release and publish-testpypi. The image ARG lists get it too. Lock: claude-agent-sdk 0.2.159 (bundles Claude Code 2.1.281, ships a win_amd64 wheel, which fixes the Windows smoke), openai-codex and cli-bin 0.156.1 (already exempt), google-antigravity 0.1.18 (image-only install, not gated), anthropic 1.8.0, uipath 2.14.24. litellm stays at 1.102.0 and harbor at 0.23.0, the newest that clear the gate. Image ARGs: Claude Code 2.1.281, Pi 0.87.1. The pyproject floors for claude-agent-sdk and openai-codex stay at 0.2.157 and 0.155.1: skills and coder_eval_uipath CI do not exempt these packages, so a higher floor would break their installs of the next release until the new versions age in. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(harbor): exempt claude-agent-sdk from the package-age gate in Harbor E2E Harbor E2E installs the locked deps on the same safe-chain pool but carried no exclusion list, so it blocked claude-agent-sdk 0.2.159. It now uses the same workflow-level expression as pr-checks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(criteria): search past the 2000-char bound only in Bash commands A non-Bash tool's params are matched as JSON, so windowing them made a long Write/Edit body count as a command: a doc mentioning a retired command past char 2000 tripped a max_count: 0 gate without a tool_name filter, and later windows dropped the "content" key that skills' api-workflow discovery patterns guard on. Those tools keep the leading 2000 chars. Re-grading is unchanged: 35 fail->pass / 0 pass->fail on the 2026-09-23 Opus 5.5 run, 0/0 on the Sonnet 5 nightly. Also records the long-command behavior in the Task Definition Guide so suites know exclude_pattern and max_count gates now see the whole command, and drops comments that repeated the constant names. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(agents): update Pi and Antigravity references for the harness bump - Pi 0.87.1: same stream event names, identical JSON serializer and the same --help thinking levels as 0.84.4, so the grammar citations move up. - Antigravity 0.1.18 enforces workspaces inside localharness, not through a Python-side workspace_only policy; docstrings and the pinned version in ANTIGRAVITY.md now say so. The rewritten workspace test only restated the test above it, so its rationale moves there and it is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5bf4a72 commit 3aa2db3

19 files changed

Lines changed: 323 additions & 149 deletions

File tree

‎.claude/notes/contracts.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,14 @@ the raw text alone can stop counting.
9595
It is memoized because the early-stop watcher re-scans the whole accumulated trajectory on
9696
every tool-call event, normalizing the same command many times per run.
9797

98-
The regex search window is capped to bound ReDoS on a large command string, and
99-
normalization runs over that same truncated window, so `shlex` never sees more than the cap
100-
and needs no separate guard.
98+
Each regex search is bounded to 2000 characters to limit ReDoS on a large command string,
99+
but the whole command is searched: the first 2000 characters, then the rest in windows that
100+
start and end on logical-line boundaries. Agents write long heredoc scripts that end in the
101+
command a task checks for (`cat > x <<EOF ... EOF` then `uip agent validate`), and a single
102+
leading window scored those as never run. Normalization runs per window, so `shlex` never
103+
sees more than the bound and needs no separate guard. Only a Bash command is windowed:
104+
another tool's params are JSON, and a later window of a Write/Edit body is file content,
105+
not a command the agent ran, so those keep the single leading window.
101106

102107
## Recording a CLI invocation
103108

‎.github/workflows/harbor-e2e.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ env:
2929
# Anthropic-credit spend off this path; DirectRoute is exercised elsewhere.
3030
API_BACKEND: "bedrock"
3131
CLAUDE_CODE_USE_BEDROCK: "1"
32+
# Same expression as pr-checks.yml (see the comment there).
33+
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }},claude-agent-sdk
3234

3335
jobs:
3436
harbor-e2e:

‎.github/workflows/pr-checks.yml‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,12 @@ env:
3838
TELEMETRY_ENABLED: "false"
3939
# The `uipath-*` pool enforces a minimum package-age safe-chain check on installs.
4040
# Workflow-level so every installing job inherits it; per-job copies are how some
41-
# jobs previously ended up with no exclusions at all. The literal is the operative
42-
# value — no secret of that name exists at repo or org level, so the bare `secrets.`
43-
# reference this replaced resolved to an empty list. (Image builds carry their own
44-
# list in docker/Dockerfile; deliberately not the same set.)
45-
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }}
41+
# jobs previously ended up with no exclusions at all. A repo secret of this name
42+
# replaces the literal when set, so claude-agent-sdk sits outside the `||`: its wheel
43+
# bundles the Claude Code CLI, and new-model support ships there faster than the
44+
# gate window. (Image builds carry their own list in docker/Dockerfile; deliberately
45+
# not the same set.)
46+
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }},claude-agent-sdk
4647

4748
jobs:
4849
quality-gate:

‎.github/workflows/publish-testpypi.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ jobs:
3838
timeout-minutes: 10
3939
env:
4040
# `uv build` resolves build deps under the pool's safe-chain gate.
41-
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }}
41+
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }},claude-agent-sdk
4242
environment:
4343
name: testpypi
4444
url: https://test.pypi.org/project/coder-eval/

‎.github/workflows/release.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ jobs:
7575
# Load-bearing on the release path: the pool enforces a package-age safe-chain
7676
# check on uv installs. Same expression as pr-checks.yml (see the comment there),
7777
# so a package can't pass PR CI and then fail the release install.
78-
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }}
78+
SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS: ${{ secrets.SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS || 'openai-codex-cli-bin,openai-codex' }},claude-agent-sdk
7979

8080
steps:
8181
# Only a real release (main) needs the app token: semantic-release pushes the

‎docker/Dockerfile‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
3030
# tag and is bumped deliberately -- mirrors the codex CLI pin
3131
# (`openai-codex-cli-bin==…` in pyproject). Leaving it at `@latest` let Docker
3232
# layer caching freeze it nondeterministically across rebuilds.
33-
ARG CLAUDE_CODE_VERSION=2.1.177
33+
ARG CLAUDE_CODE_VERSION=2.1.281
3434
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
3535
&& apt-get install -y --no-install-recommends nodejs \
3636
&& rm -rf /var/lib/apt/lists/* \
@@ -39,7 +39,7 @@ RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
3939
# Pi Node CLI, pinned — same rationale as the Claude Code pin above (the agent
4040
# binary is a dominant non-model driver of results; @latest would freeze
4141
# nondeterministically under layer caching). Node 22 + npm are already present.
42-
ARG PI_VERSION=0.84.4
42+
ARG PI_VERSION=0.87.1
4343
RUN npm install -g @earendil-works/pi-coding-agent@${PI_VERSION}
4444

4545
# uv: matches host sandbox.py's `uv venv` + `uv pip install` fast path
@@ -84,12 +84,12 @@ COPY experiments/default.yaml ./experiments/default.yaml
8484
# pulls the `uipath` SDK from public PyPI (no credentials required).
8585
ARG CODER_EVAL_UV_EXTRAS=""
8686

87-
# safe-chain min-age exclusions for the codex cli-bin pin (see pyproject [tool.uv])
88-
# and the pinned Antigravity harness. Codex and Antigravity are always installed,
89-
# so the default excludes their pinned packages from the min-age gate (the
90-
# google-antigravity release tracks the rapidly-moving Gemini harness and may be
87+
# safe-chain min-age exclusions for the codex cli-bin pin (see pyproject [tool.uv]),
88+
# the pinned Antigravity harness, and claude-agent-sdk (its wheel bundles the Claude
89+
# Code CLI). All three are always installed, so the default excludes their pinned
90+
# packages from the min-age gate (each tracks a rapidly-moving harness and may be
9191
# newer than the gate window); callers may override.
92-
ARG SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="openai-codex-cli-bin,openai-codex,google-antigravity"
92+
ARG SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="openai-codex-cli-bin,openai-codex,google-antigravity,claude-agent-sdk"
9393

9494
# All extras (codex, antigravity, litellm, and the opt-in uipath) resolve from
9595
# public PyPI per uv.lock, so the build needs no private-index credentials.

‎docker/Dockerfile.runtime‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ COPY pyproject.toml uv.lock README.md ./
5353
COPY src/ ./src/
5454
COPY experiments/default.yaml ./experiments/default.yaml
5555

56-
ARG SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="openai-codex-cli-bin,openai-codex"
56+
ARG SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="openai-codex-cli-bin,openai-codex,claude-agent-sdk"
5757
# Install the locked deps, then coder_eval itself NON-editable. An editable/path
5858
# install would leave the package source at /src (outside the kit), so the
5959
# `COPY --from /opt/coder-eval` overlay would ship a venv that can't import
@@ -66,7 +66,7 @@ RUN export SAFE_CHAIN_MINIMUM_PACKAGE_AGE_EXCLUSIONS="${SAFE_CHAIN_MINIMUM_PACKA
6666

6767
# --- 3. Node LTS + the Claude Code CLI, under the kit dir. -------------------
6868
ARG NODE_VERSION=22.14.0
69-
ARG CLAUDE_CODE_VERSION=2.1.177
69+
ARG CLAUDE_CODE_VERSION=2.1.281
7070
# Download to a file first so a bad URL fails the build loudly (a piped
7171
# `curl | tar` would mask a 404 and only surface later as a missing npm), and
7272
# verify the tarball against nodejs.org's published SHASUMS256 before extracting
@@ -109,6 +109,6 @@ RUN chmod +x /usr/local/bin/coder_eval_entrypoint.sh
109109
# label; labels don't survive `COPY --from`). The claude-code pin mirrors the
110110
# framework image — a parity test (tests/test_image_from_dockerfiles.py) enforces it.
111111
ARG CODER_EVAL_VERSION=unknown
112-
ARG CLAUDE_CODE_VERSION=2.1.177
112+
ARG CLAUDE_CODE_VERSION=2.1.281
113113
LABEL org.coder-eval.version="${CODER_EVAL_VERSION}"
114114
LABEL org.coder-eval.claude-code-version="${CLAUDE_CODE_VERSION}"

‎docs/TASK_DEFINITION_GUIDE.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,8 @@ Checks whether the agent executed specific tools/commands during evaluation. Ins
997997

998998
**Shell normalization.** For a Bash command, both `command_pattern` and `exclude_pattern` are matched against the raw command text **and** its shell-normalized form — the `bash`/`sh`/`zsh -lc "..."` wrapper stripped and shell quoting resolved with `shlex` — and a hit on *either* form counts. So a pattern like `curated_channels` matches whether the agent wrote the argument bare, `'single'`-quoted, `"double"`-quoted, or `\"escaped\"`; you do **not** hand-encode shell quoting. Because the same haystacks also feed `exclude_pattern` and the `max_count` gate, normalization is **not** purely additive: a quote-obfuscated call can now be caught by an exclusion or a `max_count: 0` gate that the raw text alone would have missed — and, conversely, an unedited `exclude_pattern` may now exclude a call it previously let through. Cross-repo suites that hand-encoded quote tolerance in their patterns should re-baseline.
999999

1000+
**Long commands.** A Bash command is searched in full: the first 2,000 characters, then the rest in windows of at most 2,000 characters that start and end on line boundaries. So a command that follows a long heredoc in the same Bash call still counts. Other tools match only on the first 2,000 characters of their JSON parameters, so a long Write or Edit body is not read as a command. This is not purely additive either: an `exclude_pattern` or a `max_count: 0` gate can now catch a call late in a long script that it did not see before, so suites should re-baseline. A match that crosses a window boundary is not guaranteed, and `^` and `$` can also match at a window edge.
1001+
10001002
**Codex limitation.** Codex agents map `Read`, `Grep`, and `Glob` tools to `shell` commands (they execute via bash), so `tool_name: "Read"` on Codex returns no matches. Use `tool_name: "Bash"` or `tool_name: null` (any tool) for Codex-compatible checks. This criterion works correctly on Claude Code agents, which emit separate `Read`/`Grep`/`Glob` telemetry.
10011003

10021004
### `cli_called`

‎docs/agents/ANTIGRAVITY.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ working directory — both required for an unattended eval.
2727
pip install 'coder-eval[antigravity]'
2828
```
2929

30-
This pulls in `google-antigravity` (pinned to `0.1.8`), whose wheel bundles the
30+
This pulls in `google-antigravity` (pinned to `0.1.18`), whose wheel bundles the
3131
platform `localharness` binary. As with the other agents the SDK is imported lazily
3232
— a base install without the extra still runs end-to-end; Antigravity tasks fail at
3333
dispatch with a clear hint to install the extra.

‎pyproject.toml‎

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ dependencies = [
4343
# explicitly since our code/tests construct httpx2 types directly. Capped to
4444
# mirror anthropic's own bound on it (`httpx2<3,>=2.0.0`).
4545
"httpx2>=2.12.0,<3.0.0",
46-
"claude-agent-sdk>=0.2.124",
46+
"claude-agent-sdk>=0.2.157",
4747
"anyio>=4.14.2",
4848
"radon>=6.0.1",
4949
"tqdm>=4.67.3",
@@ -112,7 +112,7 @@ litellm = [
112112
# Without this extra, the framework still installs and runs; Codex-dependent
113113
# code paths fail at dispatch with a clear hint pointing back here.
114114
codex = [
115-
"openai-codex>=0.144.4",
115+
"openai-codex>=0.155.1",
116116
]
117117
# Optional extra that enables Antigravity agent support:
118118
# - AntigravityAgent implementation using Google's official google-antigravity SDK
@@ -131,7 +131,7 @@ codex = [
131131
# Without this extra the framework still installs and runs; Antigravity-dependent
132132
# code paths fail at start() with a clear hint pointing back here.
133133
antigravity = [
134-
"google-antigravity==0.1.8",
134+
"google-antigravity==0.1.18",
135135
]
136136
# Optional extra that enables OpenCode agent support.
137137
#
@@ -180,7 +180,7 @@ pi = []
180180
# back here (it is not needed to EXPORT a task to Harbor format, only to run coder-eval
181181
# itself as Harbor's agent).
182182
harbor = [
183-
"harbor==0.22.0",
183+
"harbor==0.23.0",
184184
]
185185

186186
[project.scripts]
@@ -212,14 +212,14 @@ packages = ["src/coder_eval"]
212212
allow-direct-references = true
213213

214214
[tool.uv]
215-
# openai-codex 0.144.4 hardpins `openai-codex-cli-bin==0.144.4` (a stable
215+
# openai-codex 0.156.1 hardpins `openai-codex-cli-bin==0.156.1` (a stable
216216
# release; the SDK version now tracks the codex CLI version line). Naming it
217217
# here documents and holds the pinned cli-bin build — the harness binary is a
218218
# dominant non-model driver of eval results, so it travels with the coder_eval
219219
# release tag and is bumped deliberately (mirrors the antigravity localharness
220-
# and claude-code CLI pins). 0.144.4 publishes manylinux wheels (x86_64 +
220+
# and claude-code CLI pins). 0.156.1 publishes manylinux wheels (x86_64 +
221221
# aarch64), so CI/Linux installs work.
222-
override-dependencies = ["openai-codex-cli-bin==0.144.4"]
222+
override-dependencies = ["openai-codex-cli-bin==0.156.1"]
223223

224224
constraint-dependencies = [
225225
# Fix known CVEs in transitive dependencies
@@ -443,6 +443,8 @@ filterwarnings = [
443443
"ignore::DeprecationWarning", # Ignore deprecation warnings from dependencies
444444
"ignore::PendingDeprecationWarning",
445445
"ignore::ResourceWarning", # Ignore unclosed resources (handled by cleanup)
446+
# litellm>=1.102 TypedDicts mark fields ReadOnly; pydantic warns when it builds their schema
447+
"ignore:Item '.*' on TypedDict class '.*' is using the `ReadOnly` qualifier:UserWarning",
446448
]
447449

448450
# Coverage integration (when using --cov)

0 commit comments

Comments
 (0)