Skip to content

fix(agents): disable Cline auto-update so the pinned CLI holds - #4654

Merged
qiaochuz-nv merged 2 commits into
mainfrom
qiaochuz/nvb-6764103-cline-auto-update-pin
Oct 22, 2026
Merged

qiaochuz-nv merged 2 commits into
mainfrom
qiaochuz/nvb-6764103-cline-auto-update-pin

Conversation

@qiaochuz-nv

@qiaochuz-nv qiaochuz-nv commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Keeps the Cline CLI at the pinned cline_version for the whole run. Before this change, Gym's own
cline --version check started Cline's self-updater. The updater replaced the pinned 3.0.55 install
with npm latest while the run was going, and a task spawned during the swap failed with
FileNotFoundError. That surfaced as HTTP 500 and stopped collection.

Fixes NVBug 6764103.

Closes #4502

Cause

From cli-v3.0.55, every Cline CLI invocation, --version included, runs autoUpdateOnStartup()
(apps/cli/src/main.ts). If npm has a newer release, the CLI spawns a detached
npm update -g cline --tag latest --min-release-age=0 when it exits (applyDeferredUpdate() in
apps/cli/src/index.ts). Only CLINE_NO_AUTO_UPDATE=1 disables this.

The harness added in #2563 never set that variable:

  • responses_api_agents/cline_agent/setup_cline.py _cline_version ran cline --version with the
    inherited environment. The post-install pin check in ensure_cline therefore started the updater
    at server startup, with its cwd set to the agent server directory, which matches the npm log in
    the NVBug.
  • ClineAgent._env() built the environment for cline auth and the task without it, so every
    rollout started the updater again.

While npm reifies the package, bin/cline does not exist. In a local race, 185 of 349 spawns in
that window failed with FileNotFoundError while cwd and PATH were intact. Afterwards the
installed version is no longer the pin (3.0.71).

Fix

  • _cline_version passes env={**os.environ, "CLINE_NO_AUTO_UPDATE": "1"} to the version probe.
  • _env() adds CLINE_NO_AUTO_UPDATE=1 next to the other pinned CLINE_* keys. That overrides an
    inherited value, and an explicit config.env entry can still override it, as for every other key
    there. This is the same approach opencode_agent takes with OPENCODE_DISABLE_AUTOUPDATE.
  • The server's own os.environ is not modified.
  • npm install is unchanged, because it runs no cline command (postinstall.mjs only links the
    binary).
  • responses_api_agents/anyswe_agent/setup_scripts/cline_agent_deps.sh checked the pinned install with
    a bare "$DEPS_DIR/bin/cline" --version, which started the same updater against $DEPS_DIR, the
    bundle the AnySWE sandbox mounts. It now runs CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version
    (review follow-up). Changing the script also changes the AnySWE runtime-bundle recipe hash, so an
    existing, possibly unpinned bundle gets rebuilt.
  • The README notes the opt-out under cline_version. It is a default: config.env is applied
    last, so an explicit env: {CLINE_NO_AUTO_UPDATE: "0"} re-enables the updater.
  • harness_agent and anyswe_cline run ClineAgent, so its version probe and _env() cover their
    cline calls. The AnySWE install script above is their only other cline call.

Before the fix

Gym 717c04a3b (main when this PR was opened). The command runs Gym's own ensure_cline("3.0.55") against a
scratch npm prefix, with an npm wrapper first on PATH that logs argv and cwd:

PYTHONPATH=$GYM python -c 'from responses_api_agents.cline_agent.setup_cline import ensure_cline; ensure_cline("3.0.55")'
14:32:35,714 responses_api_agents.cline_agent.setup_cline cline 3.0.55 is ready at .../prefix/bin/cline
npm calls:
21:32:18.871 cwd=.../agentdir argv=install -g cline@3.0.55
21:32:32.146 cwd=.../agentdir argv=prefix -g
21:32:35.723 cwd=.../agentdir argv=update -g cline --tag latest --min-release-age=0

With the real update allowed to run, an asyncio loop spawning cline the way ClineAgent._spawn
does (90 s) recorded:

    7  rc=0 out='3.0.55'
  185  FileNotFoundError: [Errno 2] No such file or directory: 'cline'
  157  rc=0 out='3.0.71'

QA testcase cline_math (README Cline math stack, hosted policy endpoint, no GPU) on Gym
717c04a3b. The npm debug logs written during the run:

21:40:45 npm install cline@3.0.55   cwd=<checkout>/responses_api_agents/cline_agent
21:40:56 npm prefix                 cwd=<checkout>/responses_api_agents/cline_agent
21:40:57 npm update cline           cwd=<checkout>/responses_api_agents/cline_agent
(cline_math_agent)   File ".../responses_api_agents/cline_agent/app.py", line 629, in _run_cline
(cline_math_agent)   File ".../responses_api_agents/cline_agent/app.py", line 586, in _spawn
(cline_math_agent)     proc = await asyncio.create_subprocess_exec(
(cline_math_agent) FileNotFoundError: [Errno 2] No such file or directory
aiohttp.client_exceptions.ClientResponseError: 500, message='Internal Server Error', url='http://127.0.0.1:28895/run'
Collecting rollouts:   0%|          | 0/5 [00:01<?, ?it/s]

Global cline --version after the run: 3.0.71.

AnySWE setup path (h100). The Node and Cline section of cline_agent_deps.sh from main, run with a
scratch DEPS_DIR:

validation --version printed: 3.0.55
npm log: npm update cline   argv "update" "--global" "cline" "--tag" "latest" "--min-release-age" "0"
         placeDep ROOT cline@3.0.73 REPLACE
cline in DEPS_DIR 90 s later: 3.0.73

After the fix

Gym 7daa0a6a5 (this branch before the rebase; nothing in cline_agent or anyswe_agent changed on
main between 717c04a3b and c62f9cc67). Same ensure_cline("3.0.55"), then three spawns through
ClineAgent._spawn with ClineAgent._env(), with the real update allowed to run:

npm calls:
21:37:26.379 cwd=.../agentdir argv=install -g cline@3.0.55
21:37:37.530 cwd=.../agentdir argv=prefix -g
agent _env CLINE_NO_AUTO_UPDATE = 1 | os.environ: None
spawn 0: rc=0 out='3.0.55'
spawn 1: rc=0 out='3.0.55'
spawn 2: rc=0 out='3.0.55'
installed version 15 s later: 3.0.55

QA testcase cline_math on Gym 7daa0a6a5. The npm debug logs written during the run show no
update:

21:43:08 npm install cline@3.0.55   cwd=<checkout>/responses_api_agents/cline_agent
21:43:16 npm prefix                 cwd=<checkout>/responses_api_agents/cline_agent
$ gym eval run --no-serve --agent cline_math_agent --input .../environments/cline_math/data/example.jsonl ...
  exit 0 in 148.8s
    5 rollout rows with unique identities
    cline_math_agent: mean/reward 0.8 agrees with persisted rollouts
    5 real Cline trajectories; 15 live math verifier checks
PASS     5.0s  the README preparation validates every row of the complete DAPO17K train split
PASS   201.3s  the README Cline math stack evaluates all five examples with fifteen live controls and cleanup
cline_math: 2/2 passed in 206.3s

Global cline --version after the run: 3.0.55.

AnySWE setup path, same h100 run with this branch's script: --version printed 3.0.55; the only
npm log is npm install cline@3.0.55; cline in DEPS_DIR 90 s later: 3.0.55.

Validation

  • QA testcase cline_math: before FAIL (FileNotFoundError → /run 500, 0/5 rollouts, Cline left at 3.0.71); after 2/2 PASS (5 rollouts, 15 live verifier checks, no npm update, Cline stays 3.0.55)

  • responses_api_agents/cline_agent/tests + responses_api_agents/anyswe_agent/tests: 122 passed on the
    branch rebased onto c62f9cc67. New tests:

    • Version-probe env with an ambient "0", with the parent env left untouched.
    • _env() with an ambient "0".
    • cline auth and the task spawn both carrying "1".
    • An explicit config.env "0" still winning.
    • Every bin/cline call in cline_agent_deps.sh carrying CLINE_NO_AUTO_UPDATE=1.

    The first three fail on main (KeyError: 'CLINE_NO_AUTO_UPDATE', KeyError: 'env'), and the
    script test fails on main's script.

  • AnySWE setup path: the cline_agent_deps.sh A/B above. The cline_math smoke covers only the
    cline_agent path. No AnySWE rollout smoke ran, because it needs a sandbox provider and container
    formatter that h100 does not have.

  • CLINE_NO_AUTO_UPDATE A/B on the real 3.0.55 binary: unset, 0 and true each started one
    update. 1 started none.

  • pre-commit run on the changed files: passed.

  • Rollout evidence: the cline_math run above produced 5 real Cline trajectories against a hosted Nemotron endpoint, each with 1–3 tool calls, all finished_naturally=true and mask_sample=false, with rewards 1, 1, 1, 1, 0. The single 0 is a wrong answer from the model, not a plumbing failure.

Compatibility

Cline no longer self-updates when Gym runs it, by default. cline_version: null installs @latest only
on a clean machine. ensure_cline reuses any cline already on PATH when no version is requested,
and with the updater off, an existing install no longer upgrades itself either. To move to a newer
Cline, bump cline_version (and CLINE_VERSION in cline_agent_deps.sh). An explicit
env: {CLINE_NO_AUTO_UPDATE: "0"} restores the old behavior. No config or schema changes.

🤖 Generated with Claude Code

@qiaochuz-nv qiaochuz-nv added bug Something isn't working QA_fix Fix implemented by a QA team member area:agent Agent harnesses and Responses API agent behavior needs-review PR is ready for code review and waiting on a reviewer complexity:low Localized change in one scope with a small, straightforward review surface labels Oct 15, 2026
@copy-pr-bot

copy-pr-bot Bot commented Oct 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@qiaochuz-nv
qiaochuz-nv requested a review from yaoyu-33 October 15, 2026 21:49
@qiaochuz-nv

Copy link
Copy Markdown
Contributor Author

/ok to test 7daa0a6

@yaoyu-33 yaoyu-33 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.

Thanks for the thorough write-up and the before/after evidence. The fix is sound for the paths it covers: both the version probe and ClineAgent._env() now set CLINE_NO_AUTO_UPDATE=1, every cline spawn in cline_agent goes through _env() (cline auth and the task), and the three new tests assert real values and fail on main. We found no blockers.

Suggestions

  • One cline call is still unprotected: cline_agent_deps.sh (outside this diff, see the inline comment on app.py). Since anyswe_cline is called out as covered, it is worth closing here.
  • README: "every call" is true by default only; see the inline note.

PR hygiene

  • The Compatibility section says cline_version: null "installs @latest at startup". ensure_cline reuses any cline already on PATH when no version is requested (setup_cline.py#L51-L52, #L97-L99), so that only installs @latest on a clean machine, and with the updater off an existing install no longer upgrades either. Could you reword that paragraph (the README bullet already says "on a clean machine")?
  • Validation: the cline_math smoke covers the cline_agent path; there is no smoke for the AnySWE setup path.

Reviewed with an agent team plus an independent cross-review at 7daa0a6a5. 1 inline suggestion, 1 doc note.

# Cline otherwise ends every call by spawning a detached `npm update -g cline`, which
# replaces the pinned cline_version mid-collection and briefly removes the binary from
# under concurrent spawns. Only the exact value "1" disables it.
"CLINE_NO_AUTO_UPDATE": "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.

app.py:501

Nice fix here. One call is still not covered: cline_agent_deps.sh:33 runs "$DEPS_DIR/bin/cline" --version right after the pinned install, with the server's env and no opt-out. Under this PR's premise that arms npm update -g cline against the same $DEPS_DIR the sandbox later mounts, so the AnySWE recipe can still end up unpinned (the 3.0.55 → 3.0.71 drift), just once per recipe instead of per rollout. Could you prefix it?

Suggested change
"CLINE_NO_AUTO_UPDATE": "1",
CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version

(for cline_agent_deps.sh:33; GitHub can't attach the suggestion there since the file isn't in this diff.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c4d6d37: cline_agent_deps.sh now runs CLINE_NO_AUTO_UPDATE=1 "$DEPS_DIR/bin/cline" --version, with a test that every bin/cline call in the script carries it. A/B of that script section on a Linux host with a scratch DEPS_DIR: the old line started npm update -g cline and left DEPS_DIR at 3.0.73; the new one ran no update and stayed at 3.0.55. Details in the PR body.

the event parser was validated against it, so treat a bump as a deliberate change: raise it,
re-run the tests and a live eval, then commit). `null` installs `@latest`
re-run the tests and a live eval, then commit). `null` installs `@latest`. Every `cline` call Gym
makes sets `CLINE_NO_AUTO_UPDATE=1`, so Cline's self-update cannot replace the pinned install

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.

README.md:120

Small wording point: config.env is applied after this key (app.py#L513), so an explicit env: {CLINE_NO_AUTO_UPDATE: "0"} re-enables the updater, which the PR description says is intended. Could the README say "by default" / "unless overridden via env"? A one-line test pinning that precedence would also be nice.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in c4d6d37: the README now says the opt-out is the default and that an explicit env: {CLINE_NO_AUTO_UPDATE: "0"} re-enables it, and test_config_env_can_reenable_auto_update pins that precedence. The Compatibility paragraph is reworded too (cline_version: null installs @latest only on a clean machine).

@github-actions github-actions Bot removed the sla:review-overdue Review response is over the one-business-day SLA label Oct 22, 2026
qiaochuz-nv and others added 2 commits October 22, 2026 10:57
Every `cline` call, `--version` included, records a pending self-update when
npm has a newer release and spawns a detached `npm update -g cline` on exit.
The post-install version probe in ensure_cline therefore replaced the pinned
3.0.55 install mid-collection and briefly removed the binary, so a concurrent
task spawn failed with FileNotFoundError (HTTP 500, collection aborted).

Set CLINE_NO_AUTO_UPDATE=1 (the only value Cline honours) on the version
probe and in the env for `cline auth` and the task, without touching the
server's own os.environ.

Closes #4502

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
Review follow-up. cline_agent_deps.sh validated the pinned install with a
bare `cline --version`, which arms Cline's self-update against $DEPS_DIR,
the bundle the AnySWE sandbox mounts. Prefix it with CLINE_NO_AUTO_UPDATE=1.

Also say in the README that the opt-out is a default an explicit config.env
can override, and pin that precedence with a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: qiaochuz <qiaochuz@nvidia.com>
@qiaochuz-nv
qiaochuz-nv force-pushed the qiaochuz/nvb-6764103-cline-auto-update-pin branch from 7daa0a6 to c4d6d37 Compare October 22, 2026 18:02
@qiaochuz-nv

Copy link
Copy Markdown
Contributor Author

/ok to test c4d6d37

@yaoyu-33 yaoyu-33 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.

Approving at c4d6d37: the deps-script --version call now carries the opt-out, the README states the override, and tests pin both the default and the config.env precedence.

@yaoyu-33 yaoyu-33 added ready-to-merge PR is approved, current, and waiting for final CI or merge and removed needs-review PR is ready for code review and waiting on a reviewer labels Oct 22, 2026
@qiaochuz-nv
qiaochuz-nv added this pull request to the merge queue Oct 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔄 Merge queue validation started! You can track the progress here: https://github.com/NVIDIA-NeMo/Gym/actions/runs/39855678807

Merged via the queue into main with commit 2b5faf0 Oct 22, 2026
49 of 52 checks passed
@qiaochuz-nv
qiaochuz-nv deleted the qiaochuz/nvb-6764103-cline-auto-update-pin branch October 22, 2026 21:53

This branch was previously deployed

1 inactive deployment
public — c4d6d376 Deployed Oct 22, 2026 by copy-pr-bot[bot] via release / finalize / notify #6669
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent Agent harnesses and Responses API agent behavior bug Something isn't working complexity:low Localized change in one scope with a small, straightforward review surface QA_fix Fix implemented by a QA team member ready-to-merge PR is approved, current, and waiting for final CI or merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[NVBug 6764103] Force CLINE_NO_AUTO_UPDATE in cline_agent to hold the pinned CLI

2 participants