Saphan StudioDocs
The console

When it refuses

The previous console's four sign-in states and what each one means, the terminal endpoint's refusals, and the missing-string check.

This page is about the previous console, served by the projection server (saphan server start, or the saphan-server daemon).

The four sign-in states, and what each one means

Run saphan console admin status first. It distinguishes the states that look identical from the browser.

The browser shows 503 and "no master-admin credential has been set"

status says NOT SET. Nothing is wrong; the console has simply never been provisioned in this workspace. Set the credential — see Signing in.

The page itself carries the way out — the verb to run, the fact that it asks rather than taking a flag, and the restart — numbered, so you do not have to come here to find it. It names the workspace by its short name only; the absolute paths are deliberately not on that page, because anyone who can reach the port can read it. saphan console admin status, run in a shell on the host, prints them.

If you set the credential and the page does not change, you have met the one-per-process resolution: restart the server. The page says so as step 2. This catches people on their first console, not only on a rotation.

Note that <state> is per workspace. A credential set in one workspace does not open the console of another.

saphan server refuses to start, naming the console

Something like:

server: console: consoleauth: credential file has loose permissions: /…/console/admin.cred is 0644 (want 0600) — refusing to load a group/other-reachable credential

This is deliberate and it is not negotiable by configuration. A credential the engine cannot trust leaves exactly two alternatives — serve /console open, or serve it shut without saying why — and both are worse than refusing to start. Fix the custody:

chmod 600 <the path the refusal names>

Then start the server again. If the file is corrupt rather than mis-permissioned (malformed), set the credential again with saphan console admin set; that overwrites it.

An absent credential never stops the server. That is the first state above: the server starts, everything else serves, and the console says it is closed.

The login form appears, the password is right, and it still refuses

In order of likelihood:

  1. Wrong username. The refusal is identical for both by design, so status is what tells you the username on file.
  2. A tripped attempt limit — but that answers 429 with a countdown, not the ordinary refusal. Wait for the countdown; see Signing in.
  3. The credential was rotated in another terminal and the server was never restarted This is the common one, and it looks backwards: the server is still verifying against the password it read at start-up, so the NEW password is refused and the OLD one works. status shows the new credential — it reads the file, which is not what the running server is using. Restart the server.

Login "succeeds" and the browser lands straight back on the login form

The browser is discarding the session cookie. This is almost always the Secure cookie rule (see What the console does not protect against): the console is being served over plain HTTP to a host that is not loopback, so the cookie is issued Secure and the browser drops it on an insecure connection.

The login page states its own cookie mode, at the foot of the form — read it. If it says the connection is plain HTTP and not loopback, serve the console over HTTPS or reach it over loopback (an SSH tunnel is the usual answer):

ssh -N -L 7654:127.0.0.1:7654 user@the-fleet-machine

Then open http://127.0.0.1:7654/console on your own machine.

A console screen is missing a string — where that is said

The engine checks its embedded interface bundle — tokens, both string catalogues and the screen inventory — at the start of every saphan process, before any verb runs. A bundle that fails that check stops every command with exit 2 and a class="ui-assets-parity" line, whichever verb you typed.

A screen key that resolves to no string in either catalogue, and is held open on purpose, is reported more narrowly: only the verbs that render or serve screens print the notice —

saphan console …      saphan serve      saphan server …      saphan serverd …

Ask for it directly, from anywhere, at any time:

saphan checks ui-assets

It loads the same embedded bundle, prints the screen-key counts and how many commands hear the startup notice, names every unresolved key with the reason it is held open, and exits 2 while any key is unresolved — so it is safe in a gate or a CI step. A sound bundle is not silence: it prints its counts and the word ok, and exits 0.

The terminal endpoint's refusals

Every refusal below happens before the connection becomes a websocket, so what you see is an ordinary HTTP status, not a socket that opens and dies.

what you seewhat it meanswhat to do
303 to /console/loginno valid sessionlog in at /console/login; if you were logged in, the server was restarted — the session table is in memory by design
403 "that Origin is not this console"the handshake came from a page served somewhere elseopen the console at its own address; this is the cross-site protection, and it is working
403 "…names its Origin"no Origin header at all — so, not a browserif you are driving this by hand, send Origin: carrying the exact console address you dialled
400 "that is not a machine id"?machine= is not a registry name in shape — it has a space, a slash, a semicolonuse the id saphan machine list prints
400 "<id> is not an admitted machine"the id is well formed and the registry does not carry itsaphan machine list prints the ids that exist; saphan machine admit is what adds one. This is a security refusal, not a typo check — see The live terminal
400 "<id> was RETIRED and is off the roster"the machine is in the registry, marked retiredre-admit it (saphan machine admit) if it is back in service; retirement keeps the row for the record and takes no work
400 "<id> is admitted with transport local"that machine is not one this engine dials over sshuse ?machine=local for a terminal on this host
400 "<id> … carries no ssh host" / "names no ssh key"an admitted row the fleet's ssh config projects no Host stanza forsaphan machine renew --machine <id> --ssh-key <path> --renewed-by <you>
400 "no machine has been admitted in this workspace yet"there is no machine registry at allsaphan machine admit first. ?machine=local still works — the near path needs no registry
400 "<id> carries the master seat <seat>"a seat on that machine is registered role=masterthe fleet reserves it; saphan machine seat --role executor is the owner act that changes the row. ?machine=local meanwhile — see The live terminal
400 "<id>'s seat <seat> last ran out of lane at …"the run ledger reads that seat's model allowance as still spentwait — a spent lane heals by itself and there is nothing to fix. saphan fleet usage asks the vendor directly; ?machine=local meanwhile — see The live terminal
400 "the run ledger could not be read"the gate could not ask whether the lane is spenta gate that cannot ask its question does not answer it with a pass; check the store (saphan fleet dispatchable reads the same ledger)
400 "cols must be a whole number…"the window is out of range1–1000 columns, 1–500 rows
405you used something other than GETthe terminal is a GET upgrade
500 "…needs HTTP/1.1"the connection cannot be upgradeddo not put an HTTP/2-only proxy in front of /console/pty
503 console closedthe credential cannot be readsaphan console admin status, then restart the server

And two that arrive after the socket is open, as a 0x02 notice, because by then there is no status left to send:

  • "the terminal could not be opened: …" — the host has no pseudo-terminal available, or the shell could not start. On the remote path this is also what an ssh failure looks like; the reason follows the colon.
  • "that window (…) was ignored" — a resize outside 1×1..1000×500. It is refused rather than clamped, so the browser and the terminal never disagree about geometry.