Skip to content
Scalpels home

Scalpels is officially launching on October 31, 2026. Special early adopter pricing is available now.

← All guides

Guide

A Comprehensive Agent's Guide to Laravel Cloud

Work in the right organization

· Ed Grosvenor

An agent that works for more than one Laravel Cloud organization will eventually have more than one API token stored. From then on, the organization a command acts on is decided by things you might not be looking at: an environment variable, the number of tokens, and which directory you happen to be in. There is no --org flag on ordinary commands.

We have seen what happens when this goes wrong. One of our own applications ran inside a client's organization for about seven weeks before anyone noticed. A worker bound one of our production apps to a checkout by picking an ID out of a list. An unknown environment ID wrote two variables into a client's production environment. None of these produced an error.

Where the CLI keeps credentials

  • ~/.config/cloud/config.json holds every stored token in plain text, as an array under api_tokens. Never read, print or copy this file. Use the CLI's own commands instead.
  • LARAVEL_CLOUD_TOKEN, if it is set and not empty, is used instead of the stored tokens.
  • .cloud/config.json in a repository holds organization_id and application_id, and sometimes environment_id. It contains no credential. It is a pointer that tells the CLI which of your tokens, and which application, to use.

cloud auth:token --list --json -n shows the organization name behind each token, the token masked to its last four characters, and where it came from: the config file or LARAVEL_CLOUD_TOKEN. It is safe to run and it is the first thing to run.

How the CLI picks an organization

This is the order in v0.6.3, read from the source:

  1. If LARAVEL_CLOUD_TOKEN is set, it wins. The stored tokens and .cloud/config.json are ignored. Nothing checks that the variable's organization matches the repository you are in.
  2. If exactly one token is stored, it is used. .cloud/config.json is not consulted for the organization at all. A single token for the wrong organization is used silently.
  3. If several tokens are stored, the CLI asks Cloud which organization each one belongs to and silently removes any token that is rejected with a 401. Then:
    • if only one token is still valid, it uses that one;
    • otherwise it uses the token whose organization matches organization_id in .cloud/config.json;
    • otherwise, when you are non-interactive, it fails with Multiple API tokens found. A human at a terminal gets a picker instead.

The .cloud/config.json it reads is at the root of the git repository containing your current directory, or in the current directory itself if you are not inside a repository. That has two consequences:

  • Running from a subdirectory of a repository uses the repository's binding, not anything in the subdirectory.
  • A scratch directory that sits inside another repository, such as a temporary folder inside a project checkout, uses that project's binding, not the file you just wrote.

Pin the organization with a scratch directory

When more than one organization is authenticated, run every command from a directory you created for that organization, outside any other repository:

mkdir -p ~/cloud-targets/acme && cd ~/cloud-targets/acme
git init -q
git remote add origin https://github.com/acme/shop.git
mkdir -p .cloud
printf '{"organization_id": "%s"}\n' "org-…" > .cloud/config.json

The git init makes this directory its own repository root, so the CLI cannot reach past it. The remote is needed because cloud deploy refuses to run in a directory without a git remote, even though nothing is pushed from it. Add application_id to the file only when you need commands to default to one application.

Write the file by hand rather than with cloud repo:config. repo:config needs an application to exist in the organization, so it cannot bootstrap an empty one, and in a real repository it writes a binding into a file other people may commit.

Prove the target before every write

Pinning tells the CLI what to use. It does not prove you pinned the right thing. Before any write, and again before any destructive run:

unset LARAVEL_CLOUD_TOKEN
cloud auth:token --list --json -n
cloud application:list --json -n --fields=id,name
  1. Confirm that LARAVEL_CLOUD_TOKEN is not set, or that its organization is the one you want. auth:token --list shows the variable as a source when it is set.
  2. Confirm the organization name you expect is among the authenticated ones.
  3. Confirm the application list contains applications you were told to expect, by full ID, not just by name. Names repeat across organizations. Two organizations can both have an app called api.

The CLI has no command that prints the current organization's ID. If you are calling the API directly, GET /api/meta/organization returns the token's organization id, name and slug. Read it at the start of the task, compare it with the ID you were given, and refuse to write on a mismatch.

Record what you verified, along with the CLI version and a timestamp, before you act. The record makes your later claims checkable.

Identifiers that resolve to something else

An identifier that does not exist should be an error. Several are not.

  • Environment IDs before v0.6.2. If the ID you passed did not resolve, the CLI fell through to the environment tracking your current git branch, and then to the application's only environment. Writes went there. This is how a deliberately fake ID wrote two variables to a client's production environment. From v0.6.2, an unresolved environment ID or name is an error.
  • Application IDs, still in v0.6.3. If an application ID does not resolve, the CLI falls back to the application whose repository matches your current git remote, then to the organization's only application. A .cloud/config.json naming a deleted application, which is common after a teardown or a reprovision, can therefore resolve quietly to a different application.
  • Command IDs, still in v0.6.3. An unknown command ID falls back to the environment's command list and silently picks the only one if there is just one.
  • The Cloud API. A PATCH to an environment ID that Cloud does not recognise has been observed to write to the application's default environment. GET the identifier back and check its name before you write to it.

Two habits close all of these:

  • Pass only identifiers you read back in this session. Never copy an ID from a brief, a note or yesterday's log without resolving it again first. Never pick one out of a list because it looks right.
  • Check the identifier in the command's own output. In human-readable mode the CLI prints the application and environment it resolved. With --json, include the identifier fields in --fields and compare them with what you passed. If they differ, stop.

Repository bindings go stale and travel

.cloud/config.json is a pointer, so whether to commit it is a judgement call. Committing it means every checkout of a private repository you own targets the same application. But:

  • Never commit it to a public or template repository. Every clone and fork then resolves to the original author's application. We shipped this exact defect in a public example and found it during a clean-room test.
  • Bindings outlive the apps they name. After an application is deleted or reprovisioned, the committed file still names the old ID. Combined with the application fallback above, that is how commands land somewhere unexpected.
  • Branches unbind checkouts. If the file is committed on a branch but not on main, switching to main deletes it from the working tree. An untracked local copy can also block a pull that adds a tracked one.
  • Worktrees and fresh clones have no binding if it was never committed.
  • cloud ship does not write the file. Run cloud repo:config afterwards if you want a binding.

When you work for several organizations

The pattern that worked for us when one session touched two organizations:

  • Scope the task to one organization explicitly, by name and ID, and treat every other authenticated organization as read-only.
  • Use one scratch directory per organization, and change into it for every command rather than relying on where the previous command left you.
  • Re-run the identity check before each destructive step, not just at the start.
  • Never let a cache, a variable or a summary carry an ID from one organization's work into another's. We had a cache keyed on our own tenant rather than on the Cloud organization, and it showed one account's application names after the token had been swapped for another's.

Want some help?

After you buy, we will pay for an hour with one of these experts to help you get everything up and running.

  • Artisan Build

    USA

    We run Scalpels, as well as a suite of other tools for developers and development teams.

    Agency Ask for help
    Ask Artisan Build for help