Head Ventures mark: a cream H split by a brass rule Head Ventures Crew Install

Guides

Cloud agent environment setup fails with a 400 — and the file that usually did it.

TL;DR. Three different-looking failures — “Failed to save: 400” during environment setup, an agent that boots from the IDE but dies from web/Slack/GitHub, and Docker COPY errors like failed to calculate checksum … not found — trace back, more often than not, to one thing: a committed .cursor/environment.json that outranks whatever you saved in the dashboard and points somewhere stale. Diff the committed file first — git show origin/main:.cursor/environment.json — then work the checklist below. If your problem is one step later (the environment boots but the agent can’t read the GitHub issue), that’s a different guide.

The symptom, in its three usual shapes

  • The 400 at save. You run environment setup from the web, click “Save to team”, and get “Failed to save: 400”. Re-running setup produces the same wall. There’s a public thread for exactly this, opened in May 2026 and still collecting “getting 400 too” replies in August.
  • Works in the IDE, dies everywhere else. The snapshot boots fine when you start a cloud agent from Cursor Desktop, but launching from the web, Slack, or a GitHub comment returns “Agent encountered an error … We encountered an unexpected error repeatedly” — with no VM you can open to debug. That’s the “fails to boot” thread from February 2026.
  • The Docker build dies on COPY. With a custom Dockerfile environment, the build log shows the Dockerfile transferring fine but transferring context: 2B — an empty build context — and then every COPY fails with failed to calculate checksum of ref …: not found, ending in “Docker build errored (exit code: 1)”. That’s the build-context thread from July 2026.

What usually actually broke

Cursor resolves environment configuration in a strict order, and the setup docs spell it out: a .cursor/environment.json committed in the repository wins first, then a personal saved environment, then the team one. The committed file doesn’t merge with what you configured in the dashboard — it replaces it. Most of the pain in those three threads is that file quietly outranking everything while pointing somewhere that no longer exists.

Stale or placeholder snapshot references. On the 400 thread, the reply from Cursor’s side calls it a known class of bug: the repo may carry a .cursor/environment.json that points cloud agents at an old or expired environment snapshot, and because the file takes priority, saving a fresh team environment keeps failing until the stale reference is removed or updated. On the fails-to-boot thread the same mechanism wore a different mask: the committed file contained a placeholder value instead of the real snapshot ID. The IDE kept working because it read the local file; web, Slack, and GitHub runs read what’s committed — which is why the split between “works here, fails there” is itself a diagnostic.

A build context that doesn’t contain your COPY sources. In environment.json, the dockerfile and context paths are relative to the .cursor directory — but the values ., ./, and .. are special-cased to mean the repository root. So "context": "." does not mean “the .cursor folder”, and bare-filename COPY sources that live in .cursor/ silently vanish from the build. That’s what the July thread hit; Cursor staff explained the special-casing, and the docs were updated as a result. The tell in the log is transferring context: 2B.

Secrets living in the wrong place. Credentials pasted into environment.json, committed .env files, or values baked into a snapshot all fail in ways that are hard to see from the outside — and they’re a leak besides. The official docs are plain about the recommended path: secrets go in the dashboard, arrive as environment variables, and only land in agents started after you save them.

One honest caveat before the checklist: not every 400 is this file. On the same 400 thread, two reporters say they never had a .cursor/environment.json at all, and one posted the response body — the 400 unwrapped to “You do not have access to this repository.” on a repo their integration could otherwise reach. So the file is the first suspect, not the only one. Which is why step one is:

The checklist

1 — Read the actual error, not the status code

Open your browser’s devtools (Network tab), re-trigger the failing save, and read the response body of the 400. A config problem and an access problem look identical from the toast message and completely different in the payload. If the detail says “You do not have access to this repository”, stop debugging environment.json: recheck the source-control integration and your repository permissions in the Cursor dashboard — and if those genuinely check out, add your case to the thread with the payload rather than thrashing on config that isn’t the problem.

2 — Inspect the committed file, not your local one

The IDE reads the .cursor/environment.json on your disk; web, Slack, and GitHub-triggered agents read the one committed to the repo. Look at what the cloud actually sees:

git fetch origin
git show origin/main:.cursor/environment.json

Swap main for the branch your cloud agents run from. If the committed file differs from your local copy — a placeholder snapshot ID is the documented case — you’ve likely found it. Builds read the configuration from the environment’s default branch, so the fix has to be pushed there, not just saved locally.

3 — Pin a real snapshot, or delete the file and re-save

Two clean exits, straight from the staff reply on the 400 thread:

  • If your team isn’t intentionally using the file for a custom cloud-agent setup: delete .cursor/environment.json, push, run environment setup again, and click “Save to team” again. With the override gone, agents use the freshly saved team environment.
  • If your team needs the file: replace the stale snapshot reference with a current snapshot ID — it’s on the environment’s page in the Cursor dashboard — and push. A healthy snapshot-based config is small:
{
  "snapshot": "snapshot-20260212-00000000-0000-0000-0000-000000000000",
  "install": "npm install"
}

Treat the snapshot ID like a pinned dependency: when you rebuild the environment, updating this file is part of the change, not an afterthought.

4 — In Docker builds, leave context out unless you mean the repo root

This pattern fails, even though every file exists in .cursor/:

{
  "build": { "dockerfile": "Dockerfile", "context": "." }
}

# .cursor/Dockerfile
COPY install.sh /home/ubuntu/install.sh
# → failed to calculate checksum of ref …: "/install.sh": not found

Because . is special-cased to the repository root, the build context doesn’t include the files sitting next to your Dockerfile. The fix that resolved the thread: remove the context line entirely. It defaults to .cursor, and bare-filename COPYs work unchanged. If you genuinely need repo-root context, set "context": ".." and prefix COPY sources with .cursor/. And per the docs: don’t COPY the whole project into the image — Cursor manages the workspace and checks out the right commit itself.

5 — Secrets go in the dashboard, and PATs never go in git

Anything the agent needs at runtime — API keys, database URLs, a GitHub token — belongs in Cursor dashboard → Cloud Agents → Secrets, where it’s injected as environment variables. Not committed in environment.json, not a .env pushed to the repo, and preferably not baked into a snapshot: the docs note that .env.local files can get captured when a snapshot is taken, and recommend the Secrets tab instead. Two operational details that bite: secrets are team/workspace-scoped, and they only reach agents started after you saved them — restart the run. If the secret in question is a GitHub token for reading issues, scope it read-only and see the issues guide for the exact permissions; a personal access token committed to the repo is a credential leak, not a workaround.

6 — Re-test from the entry point that failed

The February thread is the cautionary tale: the setup wizard and the IDE booted the snapshot fine the whole time, because they weren’t reading the broken committed file. After you push the fix, start a fresh agent from the web (or the @cursor comment, or Slack — whichever path was failing) and let that be the verdict. “It works from my IDE” verifies the wrong thing.

The environment boots. Now the agent has to read the ticket.

Once setup saves and the VM comes up, the next wall for a lot of teams is one step downstream: the agent still can’t read the GitHub issue it’s supposed to fix, because the cloud VM’s token often lacks issues scope — so it guesses. That’s a different failure with its own guide (two working fixes, including the token setup). The zero-setup version: paste the whole ticket into the launch prompt — Paste the Ticket, the free builder on this site, does the formatting and adds the guardrails. No account, nothing leaves the page.

FAQ

Why does the same snapshot work from the IDE but fail from web, Slack, or GitHub?

Because two different files are being read. The IDE uses the .cursor/environment.json on your local disk; web, Slack, and GitHub-triggered agents use the one committed to the repo. If they’ve drifted — in the public thread, the committed one held a placeholder instead of the real snapshot ID — the IDE passes and everything else fails. Diff them (step 2) and push the fix to the branch cloud agents run from.

We don’t have a .cursor/environment.json at all and still get the 400.

Then you’re past the common case, and you’re not alone — two reporters on the thread say the same. Read the response body of the failing request (step 1): one user’s 400 carried “You do not have access to this repository” even though their integration otherwise worked, which points at access, not config. Recheck the integration and repo permissions, and if they’re genuinely right, report it on the thread with the payload. We won’t invent a fix here that the thread doesn’t support.

Should we bake .env.local into the snapshot so agents have our variables?

It works — the docs confirm files present at snapshot time can be captured — but it’s the wrong default. Values frozen into a snapshot are invisible, unrotatable, and stale the day after you take it. The recommended path is the Secrets tab in the Cursor dashboard: injected as environment variables, team-scoped, and updatable without rebuilding anything. Remember new secrets only land in agents started after you save them.

Is a “background agent” different from a cloud agent?

Same thing, older name — the docs note cloud agents were formerly called background agents. Useful to know when searching: some of the best troubleshooting threads (including the fails-to-boot one cited here) live under the old name.

If you’d rather not own this class of problem

Everything above is the do-it-yourself path, and it works. Crew Install is the alternative: a seven-day, $4,500 setup where we wire Cursor cloud agents on one repo you own — environment configuration included, so 400s and stale snapshots become our problem during the install — and one ticket type starts coming out as branches, tests, and pull requests. A human on your team still merges every PR. We never touch main.

If you’d rather have it wired for you

Email orders@imarand.com with the repo and the ticket type. Plain answers, including “not a fit” when it isn’t. Doing it yourself? The $99 Merge Crew pack is the skill, the prompt, and the how-to.

Email orders@imarand.com Merge Crew pack — $99 Install by email · pack via Stripe.