Skip to content

feat(fix): fetch the Agent SDK on the first fix, never fall back silently (UPT-108) - #237

Merged
lucasbraum merged 3 commits into
mainfrom
lucasbraum/upt-108-sdk-executor-by-default
Oct 18, 2026
Merged

lucasbraum merged 3 commits into
mainfrom
lucasbraum/upt-108-sdk-executor-by-default

Conversation

@lucasbraum

Copy link
Copy Markdown
Collaborator

UPT-108. npx uptide@0.15.1 fix never installed @anthropic-ai/claude-agent-sdk (an optional peer), so the Anthropic default quietly ran the patch executor while the notice and docs described the SDK (pilot round 3).

What changes

  • The SDK is fetched on the first fix that needs it, not shipped. The package stays at 13.6 MB and keeps the SDK as an optional peer. Lookup order:
    1. An SDK resolvable next to uptide. The Action's install path is unchanged.
    2. <cache>/agent-sdk/<version>, using defaultCacheDir(), so UPTIDE_CACHE_DIR and XDG_CACHE_HOME apply.
    3. Otherwise the first fix that would use it runs npm install --prefix <temp> --no-save --include=optional @anthropic-ai/claude-agent-sdk@0.3.296. That goes through command(), so scripts are off and secrets are stripped from the env. The temp dir is renamed into place: the cache holds a complete install or nothing. One line reports it, with the size. Later runs reuse it. SDK_VERSION is pinned by a package test to the CLI's devDependency and peer range.
  • Never a silent switch. If the SDK can't run (the install failed, it failed to load, or there's no Claude Code binary for the platform), the default uses the patch executor. It says so on the first line of stderr and at the top of the PR body, with the reason and how to get the SDK executor. --agent sdk without a usable SDK stops before any clone instead of failing mid-run.
  • report.json gains llm.executor (sdk | patch, set whenever assisted fixes are on). llm.sdkMissing is now the reason string, where it used to be true.
  • Notice: the privacy note names the SDK only when the SDK will run, and the LLM: line names the executor.
  • Docs: fix.md, privacy.md and architecture.md, plus a new dated decision in ADR 0016 (the 2026-10-12 optional-peer decision is marked superseded). The changeset is minor.

What the first lines look like:

# first fix, nothing installed (node:22 container)
Agent SDK 0.3.296 is not installed: installing it once into /root/.cache/uptide/agent-sdk/0.3.296 … 295 MB, reused by later runs
uptide fix · zod · smoke-npm (npm)
LLM: anthropic / claude-sonnet-5-5 · executor sdk · budget $1.00

# install fails (@anthropic-ai scope pointed at an unreachable registry)
Agent SDK 0.3.296 is not installed: installing it once into /tmp/offline-cache/agent-sdk/0.3.296 … failed
warning: Assisted fixes use the patch executor, not the Agent SDK: @anthropic-ai/claude-agent-sdk is not installed, and installing @anthropic-ai/claude-agent-sdk@0.3.296 into /tmp/offline-cache/agent-sdk/0.3.296 failed (npm error code ECONNREFUSED). For the SDK executor, let Uptide reach the npm registry once to install it into its cache, or install @anthropic-ai/claude-agent-sdk@0.3.296 next to Uptide; --agent patch chooses the patch executor and drops this note.
uptide fix · zod · smoke-npm (npm)
LLM: anthropic / claude-sonnet-5-5 · executor patch · budget $1.00

The PR body of that run starts with the same note, after the title, as > ⚠️ ….

Sizes

node:22 on linux arm64, the packed CLI.

before (main) this PR
tarball 2,549,083 B 2,548,325 B
npm install of the tarball / plain npx uptide check 13.6 MB, 3 packages 13.6 MB, 3 packages, no SDK anywhere on disk
first fix that needs the SDK none (silent patch fallback) downloads into the cache once: 295 MB reported by Uptide (du: 310 MB)
later fix runs 0 B

I tried an optionalDependencies version first (77bfe7c, then reverted). Every install grew to 323.6 MB and 110 packages, check included: binary 250 MB, SDK 5.5 MB, its peers 36 MB. That's why it's fetched on demand.

Smoke (clean node:22 container, packed tarball, fake key, the npm smoke fixture)

The zod sites there are all mechanical, so no model call is made.

case result
plain npx -y file:<tgz> check exit 1 (breaking found, as expected); npx install 13.6 MB; no claude-agent-sdk* directory on disk, no agent-sdk cache
plain npx fix, first run one install line (295 MB), executor sdk; report.json has executor: "sdk"; exit 0 in 13 s
plain npx fix, second run no install line, executor sdk; exit 0 in 8 s
SDK installed next to uptide, empty cache no install line, executor sdk, nothing written under agent-sdk
fetch fails (.npmrc sends @anthropic-ai to 127.0.0.1:9) warning on the first line after the failed install line, executor patch; report.json has executor: "patch" and sdkMissing with the reason; PR body line 3 is the note; no partial cache left; exit 0
cache dir can't be created (UPTIDE_CACHE_DIR=/etc/passwd/cache) loud fallback with ENOTDIR in the reason, no crash (df41ef8). The run then exits 2 because Uptide's registry cache can't be written either. That is pre-existing and unrelated.

An earlier round, on the optional-dependency build, also confirmed that npm, pnpm 10, yarn 1 and yarn 4 dlx resolve and run the SDK from where they install it.

pnpm turbo run typecheck test for uptide and @uptide/core passes. biome check . has only the pre-existing warning and info.

Pilot rerun: Uptide arm only, packed tarball via plain npx -y file:<tgz>

Same harness as round 3, claude-sonnet-5-5, $1 (--max-cost default), human references from round 3. All three runs report executor: sdk.

repo run executor frozen install types lint tests build fork CI cost wall coverage vs human
calmcp, zod 3→4 this PR sdk ok ok ok ok ok same as human: only npm audit (×2) and dependency-review fail, both environmental $0.09 53 s 6/27
0.15.1 (#169) patch ok 5 errors ok 1 fail fail fail: env, plus Tool surface and docker $0.03 52 s 1/27
dms-saas, stripe 18→22 this PR sdk ok 10 errors ok 1 fail³ fail fail at typecheck $1.02 612 s 0/51
0.15.1 (#169) patch ok 5 errors ok pass³ fail fail at type-check $0.18 242 s 6/51
enki, react 18→19 this PR sdk ok ok ok ok (58/58) ok fails only at SonarQube Scan (no secret on forks, as human) $0.18 164 s 10/29
0.15.1 (#169) patch ok 4 errors ok ok fail fails only at SonarQube Scan $0.06 158 s 9/29

³ The MongoMemoryServer flake: private-admission.test.ts timed out at 5 s.

  • calmcp and enki: every local check now passes. Round 3 failed the type-check and build on both.
    • On enki, the session also replaced the undeclared @react-types/datepicker import with @entur/datepicker's own TimeValue. That was round 3's lockfile finding. The scorer counts those 2 hunks as "unrelated" only because the human left that file alone.
  • dms-saas got worse, and not because of the executor's edits. Four SDK sessions ran (the site session plus 3 feedback rounds). The gate rejected the first three with tests fail: packages/dms-saas; the local check shows that same flaky test failing. The fourth stopped at the budget. Every edit was discarded, so the branch is only the version bump, the formatter and the lockfile. That gives 0/51 coverage and the full set of upstream type errors. The gate letting one known flaky test throw away correct-looking sessions is its own follow-up, not part of this PR.
  • Cost: dms-saas ended $0.02 over the $1 limit. The last SDK session stopped at error_max_budget_usd after crossing it.
  • Not rerun after the redesign: the pilot ran on the optional-dependency build (77bfe7c, tarball sha256 a2408063…). The executor path didn't change after that: same SDK 0.3.296, same binary, same session code. Only where the SDK is imported from changed, and the smoke above covers that.
  • Fork PRs, uptide-bench only: calmcp#9, dms-saas#11, enki#8. The harness change (an UPTIDE_SPEC override, plus executor read from llm.executor) was applied to a copy of bench/, not committed.

For review

  • Terms: ADR 0016 has an open question to Anthropic, "Is an npm dependency 'preinstalling' Claude Code in a product?". Installing the published package on first use is the same question in another form. The ADR records it as still open.
  • Windows: the fetch spawns npm.cmd without a shell, which is untested. If it fails there, the run falls back loudly.
  • Pre-existing, not touched: UPTIDE_CACHE_DIR under /proc makes check and fix spin forever before any output.

🤖 Generated with Claude Code

lucasbraum and others added 3 commits October 18, 2026 10:56
…UPT-108)

`npx uptide fix` never installed @anthropic-ai/claude-agent-sdk, an optional peer, so the
Anthropic default quietly ran the patch executor while the notice and docs described the SDK.

- The SDK is an optionalDependency (pinned to the tested 0.3.296): npm, pnpm and yarn install
  it with uptide, binary included. It stays external and loads only through import().
- sdkUnavailable() says why the SDK cannot run: not installed, failed to load, or no Claude
  Code binary for the platform. A fallback is the first line of the output and of the PR
  body, with the reason and how to get the SDK executor. `--agent sdk` stops before any clone.
- report.json records llm.executor (sdk or patch) and the fallback reason in llm.sdkMissing.
- The privacy notice names the SDK only when it will run; the LLM line names the executor.
- ADR 0016 records the new decision with measured install sizes; fix, privacy and
  architecture docs follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
…t (UPT-108)

As an optional dependency the SDK made every `npx uptide` 323.6 MB, `check` included, which
never uses it. The package goes back to 13.6 MB and an optional peer:

- An SDK resolvable next to uptide wins (the Action's install path is unchanged).
- Otherwise the first fix that would use it installs the pinned SDK_VERSION into
  <cache>/agent-sdk/<version> (npm install into a temporary directory, scripts off, secrets
  stripped, renamed into place), prints one line with its size, and loads it from there.
  Later runs reuse it.
- If that install fails (offline, a proxy), the run falls back to the patch executor loudly,
  as before, with the reason and the remedy.
- A package test pins SDK_VERSION to the CLI's devDependency and peer range.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
… throwing (UPT-108)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Lucas Braum <lucasbraum@gmail.com>
@lucasbraum
lucasbraum merged commit 717468a into main Oct 18, 2026
28 checks passed
@lucasbraum
lucasbraum deleted the lucasbraum/upt-108-sdk-executor-by-default branch October 18, 2026 18:58
@uptide-release uptide-release Bot mentioned this pull request Oct 18, 2026
lucasbraum pushed a commit that referenced this pull request Oct 18, 2026
This PR was opened by the [Changesets
release](https://github.com/changesets/action) GitHub action. When
you're ready to do a release, you can merge this and publish to npm
yourself or [setup this action to publish
automatically](https://github.com/changesets/action#with-publishing). If
you're not ready to do a release yet, that's fine, whenever you add more
changesets to main, this PR will be updated.


# Releases
## uptide@0.16.0

### Minor Changes

- [#237](#237)
[`717468a`](717468a)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `npx uptide fix`
gets the Agent SDK executor. The package stays as small as before:
`@anthropic-ai/claude-agent-sdk` is not installed with Uptide. An SDK
installed next to Uptide is used as it is; otherwise the first `fix`
that needs it installs the version Uptide is tested with into Uptide's
cache (`~/.cache/uptide/agent-sdk/<version>`), says so in one line with
its size, and later runs reuse it. When that install fails (offline, a
proxy) or the SDK cannot run, `fix` runs the patch executor and says so
on the first line of its output and at the top of the PR body, with the
reason and how to get the SDK executor. `report.json` records
`llm.executor` and, after a fallback, the reason in `llm.sdkMissing`.
The privacy notice names the SDK only when the SDK will run, and the LLM
line names the executor. `--agent sdk` without a usable SDK stops before
any work instead of failing mid-run.

- [#239](#239)
[`e1e8c40`](e1e8c40)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `uptide fix` runs
only with the Claude Agent SDK; if it can't, it doesn't run. Before the
first download, an interactive terminal asks once: `uptide fix needs the
Claude Agent SDK (about 300 MB, downloaded once to <cache>). Download
now? [Y/n]` (default yes). With `--yes`, or without a terminal, it
downloads and says so in one line. Once cached, it never asks again.
Answering `n`, an SDK that can't be fetched or loaded (offline, a proxy,
no binary for the platform), a missing `ANTHROPIC_API_KEY`, another
provider, or `llm.enabled: false` / `executor: patch` in `uptide.yml`
all stop `fix` with exit 2 before any clone or spending. The run prints
one message with the reason and the fix, and `--json` returns it as
`error.code: "SDK_UNAVAILABLE"`. There is no fallback to the patch
executor anywhere: the Uptide Action stops the run with the same
message. `--no-llm`, `--agent` and `--provider` are gone from help and
docs. `llm.sdkMissing` is gone from `report.json`, and `uptide init` no
longer writes `executor`.

### Patch Changes

- [#236](#236)
[`9b35b9d`](9b35b9d)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - A repository
build that fails on the final head is no longer published as a ready PR.
When it fails, `fix` builds the parent commit in the same clone to
compare. If the parent fails too, the build line says "already failing
before this upgrade" and nothing is blocked. If the parent passes, the
upgrade broke the build: verification fails, and the PR opens only as a
draft with "Build broken by this upgrade" at the top. If the parent
can't be checked out, installed or built, verification still fails and
the PR says "Build fails on this upgrade; the parent build could not be
checked" with the reason, never that the parent passed. The GitHub
Action opens these runs as draft PRs, the same way it opens agent-led
runs. The head build also skips workspaces already built on that head,
so a workspace whose output a consumer reads is no longer built twice.

- [#233](#233)
[`1ad8071`](1ad8071)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - The PR
description's build check now gives the build's actual result: passed,
failed with its first error, or not run and why. When a migration pack
doesn't require a build, `fix` still runs the repository's build once on
the final head, and `uptide verify` runs it again, and reports what
happened. "Not required by the pack" now appears next to that result
instead of replacing it. Before, the line read "build not required" even
when the repository's build failed. A build the pack doesn't require
still doesn't block verification or publishing.

- [#238](#238)
[`6f07d3f`](6f07d3f)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - An SDK session's
edits are no longer thrown away because of one flaky test. Before the
gate rejects a session on test failures, it runs the failing test files
once more, alone, with their runner and the same isolation as the full
run. A file that passes then is recorded as flaky (`llm.tasks[].flaky`
in `report.json`, and a line in the PR body's review list) and the edits
stay. A file that fails twice still rejects them, and a failure that
names no test file (a build, a timeout) is never rerun. Final
verification keeps its full gate: it runs the flaky files again, and if
one fails there, verification fails and the PR body says so. `fix` also
stops starting verification feedback sessions when what is left of
`--max-cost` is below the mean cost of the run's sessions so far, so it
no longer starts a session it can't afford; the PR's feedback line says
when this happened.

- [#234](#234)
[`066deab`](066deab)
Thanks [@lucasbraum](https://github.com/lucasbraum)! - `fix` on npm no
longer loses a package the repo imports without declaring it when the
ERESOLVE retry re-resolves the lockfile. If the retry drops such a
package from the top of `node_modules`, it runs again keeping that
package and the locked packages that require it at their old versions;
if that conflicts, the first retry stands. Either way the summary and
the PR body name the package under "Imported but not declared" and
suggest adding it to package.json. `fix` never adds it for you.

Co-authored-by: uptide-release[bot] <338861287+uptide-release[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant