Skip to content

feat(remote): require a named environment, drop the implicit default - #229

Merged
outofcoffee merged 1 commit into
mainfrom
drop-legacy-remote-config
Sep 15, 2026
Merged

outofcoffee merged 1 commit into
mainfrom
drop-legacy-remote-config

Conversation

@outofcoffee

Copy link
Copy Markdown
Collaborator

Removes the environment a remote command falls back to when none is named — and, with it, the legacy config path that was the reason resolving an environment name had a special case.

Summary

  • BREAKING Every remote subcommand requires --env <name>. A command given none fails naming the flag and listing the registered environments, rather than acting on one the user did not choose.
  • BREAKING default loses its special status. The name stays legal and resolves like any other; it is simply never assumed.
  • BREAKING ~/.config/spinloop/remote.json is no longer read. LoadDefault, LoadConfig and ConfigPath are deleted.
  • The documented no-remote.json workflow keeps working and starts carrying an identifier: where --env <name> names an environment with no file, complete SPINLOOP_REMOTE_* overrides configure it and the name given becomes the environment identifier.
  • internal/fleet resolves a kind: remote node the one way everything else does — no default-environment special case.

Implementation details

The danger was already on record. remote deploy has required --env since the REMOTE-keyword removal, whose design says why: "Creating an environment binds a name to a machine; a silent default would hide that binding and risk clobbering the default environment" — rejecting an optional flag as a footgun. That applies to stopping an instance as much as deploying one; it had simply been applied to one command. Five others (start, stop, pause, restart, keep) still changed instance state with no target named.

A latent bug this fixes rather than deepens. docs/env-vars.md documents running the remote commands with no remote.json at all, configured entirely by SPINLOOP_REMOTE_*. But Config.Environment is set only by unmarshalling a file, and that field's own comment says the shared Lambdas "reject a call without one" — so that path reached the control plane with no identifier. Requiring the flag supplies exactly the missing piece: --env ci with complete overrides now works properly, where before it was quietly broken. That is why option 3 (no implicit target at all) ends up more capable than the narrower fixes, not merely stricter.

Two decisions worth a look, recorded in the design:

  • D1 — required on reads too, not only the mutating commands. A narrower change could require it just for start/stop/pause/restart/keep. Rejected: a rule with an exception list is one an operator has to remember, and the read is the rehearsal for the write — someone who runs remote status then remote stop in the same shell should not find the second means something the first did not warn about.
  • D2 — default stays a legal name. The risk was never the string, it was commands assuming it. So --env default keeps working for anyone who has one.

Explicit non-goal: no stored "current environment" to make the flag optional again. That is the implicit target under another name. A shell alias or a fleet file does the job without the CLI holding hidden state.

The failure reads:

$ spinloop remote stop
Error: no environment named: pass --env <name> (registered: dev-1, dev-2, vllm-1)

$ spinloop remote stop   # nothing registered
Error: no environment named: pass --env <name> (none registered yet — `spinloop remote deploy --env <name>` creates one)

Coverage 90.5%, unchanged.

`spinloop remote stop` with no flag stopped something — whichever instance
happened to be registered as `default`. Five subcommands changed the state of
a cloud instance while naming no target. `remote deploy` already required
--env for exactly this reason, rejecting an optional flag as a footgun; that
reasoning was applied to one command and not its siblings.

Every remote subcommand now requires --env <name>, failing with the flag and
the registered environment names rather than acting on one nobody chose.
`default` stays a legal name and loses only its privilege.

Two things fall out of the same root and go with it. The legacy
~/.config/spinloop/remote.json was a second path only the default environment
consulted, and the reason resolving a name needed a special case; it and its
readers are gone, so LoadDefault, LoadConfig and ConfigPath delete and
internal/fleet resolves an environment the one way everything else does.

And the documented no-remote.json workflow now works properly. It reached the
control plane with an empty Config.Environment — set only by unmarshalling a
file — which the shared Lambdas reject. Requiring the flag supplies the
missing piece: where a named environment has no file and the SPINLOOP_REMOTE_*
overrides are complete, the name given is the identifier.
@outofcoffee outofcoffee added enhancement New feature or request refactor labels Sep 15, 2026
@outofcoffee
outofcoffee merged commit dce038c into main Sep 15, 2026
3 checks passed
@outofcoffee
outofcoffee deleted the drop-legacy-remote-config branch September 15, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request refactor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant