carlos / platform Public

console, cli: say what the restart actually does

The door comment now claims exactly the mechanism it has: the bump is the delivery, because boxes read restart generations only when the serial moves, and a touch without one converges on the hourly floor instead. The CLI line stops promising that everything is back up in seconds — long-running tenants restart, exec-backed instances stop within seconds and respawn on their next request or scheduled run, which for an idle app may be a while and is correct behaviour rather than a delay to apologise for.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Paul Campbell 7f11c74a041f604c9250326a35ebf719ddbeb433 parent 82f9eaf
2 files changed, +27 −10
  • internal/console/apicli.go +17 −8
  • restart.go +10 −2
diff --git a/internal/console/apicli.go b/internal/console/apicli.go
index dd633b61a..2ed702bc6 100644
--- a/internal/console/apicli.go
+++ b/internal/console/apicli.go
@@ -470,11 +470,19 @@ func (s *Server) handleAPIRollback(w http.ResponseWriter, r *http.Request) {
}
// handleAPIRestart answers `carlos restart`: touch the app's restart
-// generation so every box serving it cycles that app's processes within
-// seconds (issue #113). No version moves, no config is written, no bake window
-// applies — this is the member-facing way to cycle a wedged instance, which
-// until now meant a box login the platform's own design rule forbids ("every
-// carlos command works for a user with zero infra access").
+// generation, then bump the serial so every box serving it cycles that app's
+// processes within seconds (issue #113). No version moves, no config is
+// written, no bake window applies — this is the member-facing way to cycle a
+// wedged instance, which until now meant a box login the platform's own design
+// rule forbids ("every carlos command works for a user with zero infra
+// access").
+//
+// The BUMP is the delivery mechanism, not decoration. Boxes do not poll
+// restart generations: the agent's existing 2s serial poll is what makes it
+// look, exactly as with a promote or a config write (internal/host/restart.go).
+// A touch that landed without a bump would still converge — on the hourly root
+// pass, up to an hour later — which is the floor, not the promise this door
+// makes.
//
// The doors, in order, and why this order — handleAPITrashApp's exactly:
//
@@ -537,9 +545,10 @@ func (s *Server) handleAPIRestart(w http.ResponseWriter, r *http.Request) {
return
}
s.audit(acct, app, addr, "restart", "requested a restart (cli)")
- // The request is in the bucket; this is what makes boxes look NOW rather
- // than on the hourly floor — the difference between "seconds" and "up to
- // an hour" for the one verb people type when something is stuck.
+ // The request is in the bucket; this is what makes boxes LOOK at it — they
+ // read restart generations only when the serial moves. The difference
+ // between "seconds" and "up to an hour" for the one verb people type when
+ // something is stuck.
s.bumpSerial(ctx)
writeJSON(w, http.StatusOK, map[string]any{
"requested": true, "account": acct.Sqid, "app": app,
diff --git a/restart.go b/restart.go
index 6cd3fe318..25c9e5a31 100644
--- a/restart.go
+++ b/restart.go
@@ -49,8 +49,16 @@ func runRestart(args []string) {
// and this terminal is not watching them do it. Claiming a restart here
// would be the same lie issue #110 was made of, told by the CLI instead of
// a header.
- fmt.Fprintf(stdout, "%s for %s/%s — instances cycle within seconds "+
- "(hibernating instances stay asleep and pick it up on wake)\n",
+ //
+ // It also does not promise that everything is back up in seconds, because
+ // that is not how the box behaves: long-running tenants are restarted
+ // within seconds, while exec-backed instances are STOPPED within seconds
+ // and respawn on their next request or scheduled run — which for an idle
+ // app may be a while, and is the correct behaviour, not a delay to
+ // apologise for.
+ fmt.Fprintf(stdout, "%s for %s/%s — running instances stop within seconds; "+
+ "long-running ones restart, on-demand ones respawn on their next request "+
+ "(sleeping instances stay asleep and start fresh when next woken)\n",
colorFor(stdout).ok("restart requested"), res.Account, res.App)
}