Cotal
DocsAPI Reference
Workstation

CLI spaces

space manages this machine's space records. use selects a default target across directories. status reads infrastructure and local records without starting or repairing components.

space

cotal space [--json|--plain]
cotal space show [<name>] [--json|--plain]
cotal space add <name> --server <url> [--root <dir>] [--mode auth|open] [--tls] [--allow-unencrypted-overlay] [--force] [-y|--yes]
cotal space add [<name>] (-f|--file <bundle.json> | --from <https url>) [--root <dir>] [--server <url>] [--force] [-y|--yes]
cotal space create <name> [--root <dir>] [--adopt]
cotal space forget <name>
cotal space delete <name> [--dry-run] [-y|--yes] [--json|--plain]
cotal use [<space> | --clear] [--json|--plain]

Space inventory

space lists the spaces this machine can reach: each one's broker state, server, mode, origin (started here, registered or discovered) and root, with the selected one marked. Inside a project whose root has one running registered space other than the selection, that space is marked as this folder's, since a command without --space targets it there. It probes each broker that this machine started or that was registered by hand, and reports one that does not answer as unreachable; its registration stays. A discovered space is never dialed by a listing and reads not checked. space show [<name>] shows one space in full and probes only that broker. With no name it is about this folder: the space it has registered, else the one it holds prepared or stopped, which show names although every other command there targets the selection, else the selected one. Neither writes anything. A registry record that cannot be read refuses both with SCHEMA_INVALID, exit 1, naming its file.

--json writes one document. space gives current (the selected space or null), currentRecorded, folder (this folder's space when it is marked, else null), items and prepared; each item is the row cotal status --json gives under meshes.items: space, server, mode, root, origin, tlsRequired, eventsRequired, modeChecked, current, check (reachable, unreachable or not-checked) and catalog. space show adds recordedAt, attachHost, maxSessions, storeDir, unencryptedOverlay, the login pins of a user-auth space under userAuth and the catalog's id, slug, kind and role. It leaves credential paths out. A name that is not registered is refused with TARGET_NOT_FOUND, exit 2, and so is a bare space show outside a project with nothing selected. Each entry under prepared has space, root, preparedAt, current, rootMissing, which is true when its project folder no longer exists, state and lastOperation. state is prepared for a space that never started, stopped for one whose stack ran and was stopped, and unsettled when its last up or down recorded no outcome. lastOperation names that operation and how it ended, such as `cotal down`, completed, or is null. space show of a prepared space gives those with the kind prepared-space. The listing marks such a folder (no longer exists) and names cotal space forget <space> for it instead of cotal up.

space add registers a space that runs elsewhere, so --space and use can reach it. A name space create refuses, such as one that starts with - or holds a *, given or named by a bundle, refuses with USAGE_INVALID, exit 1, before anything is written. With --server, the trust comes from --root's .cotal/auth, and the mode is auth when that folder holds the space's trust, open otherwise. --root defaults to this project. Outside one it is required, and leaving it out is a one-line USAGE_INVALID refusal whose remedy says what to pass: an open space keeps no credentials there, so any folder will do. With --file, a user-auth connection bundle exported where the space runs supplies the login pins and the server, unless --server names another address for that broker; --from fetches the space's discovery document instead, after you agree to the fetch, and needs a terminal. Before anything is written, space add checks that the address may carry credentials, that the trust composes, that the broker enforces the mode, and that the credentials open the space; a user-auth space's exchange must answer as the issuer it pins. An open broker answers for any name, so an open space must also be one the broker serves, as its streams show. A name it does not serve is refused with TARGET_NOT_FOUND, exit 2, naming the spaces it does serve. On a terminal it then shows what it will trust and asks; --yes skips that question, but not the confirmation of fetched pins. It never changes the selection.

A refusal writes nothing and carries its code: USAGE_INVALID or SCHEMA_INVALID (exit 1) for input, and CONFLICT, TARGET_UNAVAILABLE, TARGET_NOT_FOUND, DEPENDENCY_UNAVAILABLE, UNSUPPORTED, AUTH_REQUIRED or PERMISSION_DENIED (exit 2) for what the checks found. A name that is already registered is a CONFLICT, and its remedy offers --force only for a registration added by hand. A broker that does not answer is a TARGET_UNAVAILABLE, and its remedy offers --force. A broker that requires auth, with a --root that holds another space's trust, is a DEPENDENCY_UNAVAILABLE that names the space the folder holds. When no broker answers at --server, or at a bundle's server, the TARGET_UNAVAILABLE refusal says whether its host name resolves and whether anything took a connection at its port. When a broker answers there under another scheme, such as nats:// for a tls:// address, the remedy names that address, and says when it has no TLS. A port that takes no connection gets the port check as its remedy. For --server, the --force route follows either one. --force replaces a registration added by hand, never one this machine started or a catalog lists, and adds a --server space without checking its broker, which suits a space that is down right now; a user-auth space is always checked. Such a registration records that its mode was not checked: space and space show show the mode as not checked, with modeChecked: false in --json, and whoami does not state what that mode implies as fact. Declining the question is a no-op with a USER_CANCELLED warning. --json gives the registration under space, in the shape space show gives, with verified and replaced.

Local preparation

space create <name> prepares a space to start on this machine. It records the name and the root, which is --root or this folder, and makes <root>/.cotal/; cotal up in that root then starts the space under that name and drops the prepared record. It also writes the default persona into <root>/.cotal/agents when the root has none, as setup does, and leaves one you edited as it is. --json lists the files it wrote under result.written. The project and its ancestors must be physical directories owned by you or root, with no symlinks. World write refuses. Group write is allowed only on a directory you own whose group is your user private group, the Debian and Ubuntu default, and that has no extended ACL. On Linux, getent must show that the group is your primary group, lists no other member and is no other account's primary group, and getfacl must show no ACL entries beyond the base ones. That account list counts only when /etc/nsswitch.conf names files and systemd alone for passwd and group. A directory source such as LDAP or SSSD can list part of its accounts, so with one configured, group write refuses. getent and getfacl run from your PATH with a 2 second limit each, so the first check in a process can take a few seconds. A proven group holds for the life of the process. When a read is unavailable, group write refuses; install the acl package for getfacl, or remove group write. Root-owned sticky temporary directories are allowed. New control directories, the persona catalog .cotal/agents included, use mode 0700, even with a group-writable umask. An unsafe existing path refuses before the prepared record is written, without changing its permissions. The refusal names every unsafe path in the chain at once, the reason and one remedy such as chmod g-w <paths> that lists each path. A path that its group and every account can write gets chmod go-w, since chmod o-w alone would leave the group write that refuses next, and the other group and world gaps join that one command. When a symlink is the gap, the remedy names the physical path the folder resolves to. It starts nothing and leaves the selection as it is. cotal up applies the same rule to the root it starts in, before it writes anything, so it never starts a stack that down would refuse to stop from that root. cotal up starts a space only under a name of 1 to 64 letters, digits, underscores or hyphens that starts with a letter, digit or underscore, because a name that starts with - cannot be passed as a flag value. space create refuses any other name, such as café or -x, with USAGE_INVALID, exit 1, before anything is written, with a remedy that says which characters a name can use, and cotal up refuses a --space that starts with - the same way. The limit is 64 because each local file that carries a space spells its name twice over, and a file name holds at most 255 bytes. cotal up refuses a longer name that an earlier release prepared the same way, before it starts anything, and names cotal space forget and a shorter name. space add and meshes add refuse a longer name with USAGE_INVALID, exit 1, and write no record, because a space added from elsewhere is carried by the same files. It refuses with CONFLICT a name that is registered or prepared, a folder that is already a space's root, and the home folder, whose .cotal holds this machine's own records. It refuses a folder that is, or is in, a control directory, which is a project's .cotal or the Cotal home, judged by the path given and by the path its links lead to, with CONFLICT and nothing written. A home COTAL_HOME redirects may hold project folders beside its records, so under it only the home itself and its record entries (meshes, prepared, services and the like) are control paths. The next step names the project folder that .cotal belongs to. A project folder inside another project, outside its .cotal, is a project of its own. For a folder another space uses, the next step names creating it in another folder with --root <dir>, and starting, selecting or forgetting the space that uses the folder. A folder this account cannot write, such as /, refuses PERMISSION_DENIED, exit 2, with nothing written, and so does a Cotal home (~/.cotal or COTAL_HOME) it cannot write. A .cotal that links to a folder that does not exist refuses TARGET_NOT_FOUND, exit 2, naming the link and its target. A folder whose path contains a control character, such as a tab, a newline or an escape character, or a bidirectional formatting mark, which can show one path as another, refuses USAGE_INVALID, exit 1, with nothing written, in space create and in cotal up. The refusal names the character and shows the path with it escaped. space lists prepared spaces under their own heading, below a line that says no space has been started or registered yet when that is so, and space show, use and whoami accept one. Once one of them has run, the heading gets a State column, and the line says no space is running or registered. A space whose stack ran and was stopped reads as stopped in space, space show and use, with its last operation, as status reads it. A command that targets a prepared space refuses with TARGET_UNAVAILABLE, exit 2, naming the root to start it in, and so does cotal up --space <name> run from another folder. Its Next names cotal up --root <root> --space <name>, which starts it in that root from where you are, and cotal space forget <name>, which lets you start it in the current folder. When that root's last up or down was cut off before it recorded an outcome, as an up interrupted before it registered the space leaves it, the refusal says so and names cotal status in that root, which names the step that finishes or settles it. cotal up --space <name> in a folder that is already another space's root refuses with CONFLICT, exit 2. space add refuses a prepared name. A prepared space records its folder by path, and moving the folder leaves nothing in it that names the space. A bare cotal up in a folder that names no space, while the selected space is prepared in a folder that no longer exists, refuses TARGET_UNAVAILABLE, exit 2, rather than start a space named after the folder there. Forget the space with cotal space forget <name>, prepare it again in its new folder with cotal space create <name>, then run cotal up. Until then, space create of that name says its recorded folder no longer exists.

Forgetting a local record

space forget <name> removes a registration this machine keeps. It stops nothing, deletes nothing on the broker, and keeps the files under its root. It refuses a space this machine is running with LIFECYCLE_BLOCKED, because cotal down stops it, and a discovered space with CONFLICT, because its catalog keeps it. A stack cotal up started stays a prepared space at its root after that down, so the refusal names cotal down and then space forget again. A legacy root's down drops the record, and the refusal says so, unless the space is named after its folder: that down keeps the record, and the refusal names space forget again. A space whose root holds its stack journal counts as running while any stack member's process may still run. The refusal names one such member and writes nothing: the registration, the selection and the root's files stay. A member whose process is gone without its stop receipt does not block, and the result warns with STACK_MEMBER_UNSETTLED that it was never stopped cleanly. A prepared space is forgotten the same way, and the .cotal/ that space create made stays. A prepared record that cannot be read names no folder, so forgetting its space removes only that file, and the result's root is null. Forgetting the selected space clears the selection. Forget also removes the per-space preferences saved under that name, so a later space with the same name starts without them, and the receipt lists each removed key and value so you can set them again. If that removal fails after the record is gone, the run is partial and names the config unset to finish it, after the chmod when the preferences folder is one another account could write. Forget also removes the stack definitions cotal up saved for the space at its root, with their stopped instance records, so another folder can start a stack under the same name. The result lists them. The root's stack journal stays, keeping the stack's mode, address and store, and no longer names the instances forget removed. After space create in that folder, the space reads as prepared but not started, and up --no-manager and down there start and stop only the new members. A journal an earlier release left naming removed instances is not changed, and down there still refuses TARGET_NOT_FOUND. While a member was not stopped cleanly, or the root holds preserved state, they stay and the result warns with STACK_DEFINITIONS_KEPT. For a member that was not stopped cleanly, the warning names cotal down --yes in the root and then space forget again. When the stack's folder is gone, it names cotal down --space <space> --yes, which stops the stack and removes them. Forget keeps the other services saved under the name, such as one cotal service add saved, with their instance records. The result names them as keptServices, and a later space with the same name inherits them: its cotal service lists them and its up names them. A later space in another folder does not start one, as Running a service describes. cotal service remove <name> --space <space> --yes in that space forgets one. While no space has the name, cotal service finds no space for it, so remove a service before you forget its space to drop it at once. status in a forgotten space's root then selects no space and probes no broker. When nothing left in that root's .cotal/ names a space, its next step is cotal space create <name> --root <root>, because a bare cotal up there would start a space named after that folder. A command run there that finds no broker at the default address says that no space is registered or prepared for the folder and names the same step. That refusal has no Mesh: line, and its target names only the folder, because no space was resolved there.

In open or static-auth mode, an unanswered --server address that differs from the registration retained by target resolution gives TARGET_UNAVAILABLE, naming the address that did not answer. Its Next names the recorded broker and says to drop --server to reach it. If resolution retained no registration, the Next says to check the address instead. The request is not sent to the recorded broker in its place. Target resolution given --server probes only records at that address, including when --space is omitted. status --all still checks every recorded address because --all asks for that scope. This refusal wording does not describe the infrastructure report from status.

Live deletion preview

space delete <name> --dry-run reads the registered broker in open or static-auth mode. It requires the broker's mode to match the registration, and a static connection must confirm its current account and user identity. It reports the known first-party streams and bucket backing streams as present or absent, the broker-observed transfer-stream targets under a second exact-name grant in static mode, and the entries in the presence bucket. Both static connections must confirm their own identity and the same broker server ID. A failed or incomplete native read refuses instead of showing missing resources as absent. The snapshot is non-atomic and presence is not an authoritative liveness or membership check. User-auth deletion inventory and apply are not supported. The dry-run removes no live or local state, and a read that covers its whole scope exits 0. A space that is prepared and not running, including one stopped with cotal down, has no live broker to read or delete from: space delete refuses it with TARGET_UNAVAILABLE, exit 2. For a stopped stack it names cotal reset store <space> --yes, which removes the stored streams, and space forget, which removes only its local record. For a space that never ran it names only space forget, because no streams were stored.

The dry run also reads the space's workflow runs from its records bucket and lists each run that has not finished, with its recorded state. Deleting the streams removes those runs, and a run still waiting for an answer cannot be answered or resumed afterwards. To read a run's open request first, run cotal up, then cotal run show <run>. The typed confirmation says how many unfinished runs go with the space, and the receipt lists the runs it removed.

On a space this machine started, the apply refuses LIFECYCLE_BLOCKED while the space's own stack still runs here, before any broker connection. On a controller root that is any service instance of the space other than its broker, and on a legacy root any recorded daemon other than the broker. Deleting the streams under them leaves a stack that can neither start nor stop. The refusal names the running services and cotal down, then cotal reset store <space> --yes on a controller root or cotal clean store --force on a legacy one to remove the stored streams. When a service that is not one of the stack's members runs, such as one added with cotal service add, cotal down refuses too, so the refusal first names cotal service stop <name> --instance <id> --space <space> --yes for each one. With only the broker left after that, it names the delete again in place of cotal down. The dry-run opens with a verdict, as reset store's does: the apply would refuse, with that refusal's reason and remedy, or it is offered, with cotal space delete <space> --yes as Next. --json carries the refusal as applyBlockedBy and as a LIFECYCLE_BLOCKED warning.

Live deletion

Bare space delete <name> displays that impact on an interactive terminal and requires typing the exact space name. Its question, and the message under a wrong answer, wrap to the terminal with each continuation line under the prompt's bar. A script supplies the exact positional name with --yes; --json without --yes refuses before a connection. After confirmation the command reads the broker again and refuses if the stream states, transfer names, presence identities, registration, trust or broker identity changed. After a typed confirmation it also refuses CONFLICT, exit 2, when a stream's message count differs from the impact it showed, as when a message is posted while the confirmation waits; the refusal names each changed count and deletes nothing. It opens a separately minted exact-target connection, checks each stream state again before the first delete, and submits one deletion at a time. A native acknowledgement is reported as deleted; prior absence is separate, and the receipt lists only the groups that have a name. It then checks each target's stream info and enumerates transfer names again. A surviving or newly observed resource produces a partial outcome if deletions were acknowledged; an incomplete post-read stays unknown. A refused or unconfirmed delete stops the sequence without retry, reporting acknowledged names and refused, unknown or remaining targets. This is not atomic: a live process can recreate a stream after the final read. The result does not claim that processes stopped, or remove registrations, credentials or local files. Open-mode impact and receipt label that any broker client can delete the space. space forget is not a substitute for live deletion.

Ctrl-C or SIGTERM during the apply reports what the command left and exits 130 or 143; with --json the report is the envelope on stdout. Before any delete was submitted the outcome is failed and the streams are as they were. Once a delete was acknowledged it is partial, listing the acknowledged streams and any delete still unconfirmed, with cotal space delete <name> --dry-run to see what remains. After every delete was acknowledged and before the check that nothing remains, it is unknown.

The receipt's Next names the route back. On a space this machine started, the broker keeps running without the streams. Until they are created again, cotal history, cotal send and cotal run refuse TARGET_UNAVAILABLE, exit 2, on an auth or user space and on an open space this machine started: the streams are not on the broker, and the remedy names cotal up in the space's root. cotal status shows Space streams: not on the broker and puts the same step first in its Next. On a controller stack whose other members are stopped, a bare cotal up in its root creates them again, empty, on that broker, the way a broker start does, and starts the other members. Its receipt says Space streams: created again, empty, on the running broker, and --json sets result.streamsRecreated. It does so only when every one of the space's streams is gone. A broker that lost only some of them keeps them as they are, and up refuses TARGET_NOT_FOUND, exit 2, naming cotal down --yes, then cotal up, whose broker start creates the missing ones. While the other members run, as when another machine deleted an open space under the stack, a bare cotal up finds the stack running and changes nothing. The refusals and status then say the space was deleted under its running stack and name cotal down --yes, then cotal up. They say so only when the broker answers that the chat stream is not found. A broker that answers no JetStream request has not lost its streams: nats-server stops JetStream after a store write it could not make and keeps the streams in its store. send and the other commands that read the space first then refuse DEPENDENCY_UNAVAILABLE, naming the write failure the broker's log records and its repair, and a broker restart brings the history back. To drop the space instead, run cotal down --yes in its root, then space forget. On a legacy root, run cotal down, then cotal up to start it again with empty streams.

use <space> selects the space commands target when none is named, outside another space's project folder. It changes only the selection: it starts nothing and does not check the broker, so a stopped or prepared space can be selected and the command that targets it reports that. Selecting the selected space again is a no-op. A name that is not registered or prepared is refused with TARGET_NOT_FOUND, exit 2, naming the known spaces, and the selection stays. That refusal has no Mesh: heading, and its remedy names cotal use --clear only while a space is selected. Bare use reports the selection. Inside a project whose root has one registered space other than the selection, use also says that commands in the folder target that space while it runs, and --json gives it as folder; use reads no broker, so it does not say whether that space runs. cotal space and cotal status, which read its broker, give folder and meshes.folder as null when it does not answer, and the listing does not mark it as this folder's. A selection that names a space no longer registered is reported with a SELECTION_NOT_REGISTERED warning by use and space, and stays until use changes it. use --clear removes the selection, so a command targets its folder's space again. Its receipt names the space that was selected, and --json gives current: null with that previous. With nothing selected it is a no-op, exit 0. It takes no space name. A space stays selected through its cotal down: use then reports it as stopped, with its last operation, such as `cotal down`, completed, and names cotal up in its root. It stays selected until use changes it. On a controller root, up --json gives result.selection, with current, the space selected after the start, and changed, which is true when this up selected its own space because no space was selected.

Target resolution

A command run without --space targets the selection. When the selected space's broker is down it refuses with TARGET_UNAVAILABLE, exit 2, and when the selection names a space no longer registered it refuses with TARGET_NOT_FOUND. Neither reaches another mesh.

A folder's space captures the commands run there only while it runs. Inside a project whose root has one registered space other than the selection, and whose broker answers, the command targets that space, as it does with nothing selected, and the note under its Mesh: heading, a refusal's included, says the selection applies outside the folder. A space cotal space create prepared there and no cotal up started, and one whose stack there is stopped, is the target of the commands about the folder itself only: cotal up, cotal down, cotal space show and the folder part of cotal status. Every other command run there targets the selection, and the note under its Mesh: heading says what this folder holds and the cotal up that starts it. When that stack's last up or down ended before it recorded an outcome, a member's unconfirmed stop can keep cotal up refusing, so the note names cotal status there instead, whose Next names the step that finishes or settles it. cotal status there leaves the note out, in --json too, when it shows that stop's block under This folder's space. A refusal there keeps that note whole, as for a selection whose stack is stopped, which names its own cotal up under Next. --json carries the same note in warnings as FOLDER_SPACE_NOT_RUNNING, whose target names the folder's space, its state (prepared or stopped), its root and the selected space, and cotal chat, which prints no heading, names the selection it joins with that note on stderr. cotal space show there calls a registered folder space whose broker does not answer this folder's stopped space. A folder that holds the records of a stack that runs from another folder or under another Cotal home, as a copy, a moved folder or one whose space name a later up took over, is not released to the selection: commands there refuse TARGET_UNAVAILABLE and name that folder or home, as they do with nothing selected. For a stack that runs from another folder, the refusal heads with Mesh: <space> and carries target.space, naming the space this folder's records name, as up and down there do. Nor is a folder whose own stack's last up or down ended before it recorded an outcome while a member still runs, as an up cut off before it registered the space leaves its broker. That stack runs in part, so every command there targets it, as with nothing selected, and the note under its Mesh: heading says the selection applies outside the folder. cotal status there reports it as Half started or Half stopped with its Next, which for that up is cotal down --yes and then cotal up --root <root>. Inside a project whose root has several running spaces, none of them the selection, the command targets the selection. Before it acts, it warns on stderr and in its JSON warnings with SELECTION_NOT_FOLDER_SPACE, naming those spaces and --space <space>. An empty --space value, such as --space "$S" with S unset, is refused with USAGE_INVALID, exit 1, by every command that takes --space, before anything is read or written. It never falls back to the selection. A --server URL with a user name or password in it, such as nats://<user>:<password>@<host>:<port>, is refused with USAGE_INVALID, exit 1, by every command that takes --server, before anything is read, written or dialed. The refusal does not repeat the value. The NATS client uses only the host and port of that URL, so the command would otherwise connect with the credential it resolves. Pass that credential with --creds, a login or the space's own trust instead. up refuses such a URL with its own message. The manager reads COTAL_SERVER when --server is unset, and refuses a COTAL_SERVER with a user name or password the same way, naming COTAL_SERVER. space add and meshes add refuse such a server in a connection bundle, and the meshes add prompt refuses one typed there, in the same words and with the same next step.

A prepared space whose project folder was deleted cannot start. A command that targets it, persona included, refuses with TARGET_UNAVAILABLE, exit 2, saying the folder no longer exists and naming cotal space forget <space>. cotal space create <space> --root <dir> then prepares it again. use, space show, up --space and space forget also say the folder no longer exists, and space show names the same forget and create steps in place of cotal up. Run from the folder it was moved to, whose stack journal names the old folder, such a command says the folder was moved and names the next step that up and down name there.

With nothing selected, a command run in a project targets the space registered for that project's root. A root with no registration whose .cotal/ holds the stack journal cotal up writes is refused with TARGET_UNAVAILABLE, exit 2, naming the broker the journal recorded and cotal space create <space> --root <root>, which prepares the journal's space there again so that cotal up starts it. That is the state after an up that failed to start or a space forget, and the command never dials the default address instead. status there reads the machine, selects no space and probes no broker. A legacy root after its down, which drops its registration, is refused the same way, naming the space its account names, the address in the broker configuration its last start rendered and the bare cotal up there that starts it at that address again. A space named after its folder keeps its registration, so a command there is refused TARGET_UNAVAILABLE naming that root and cotal up, and status names the same step. A selection does not change these refusals: a root that holds such a journal or configuration and no registration, as a moved project does, is refused the same way and never reaches the selected space.

A copied or moved project folder keeps a stack journal that names the folder it was recorded in. space create <name> there refuses CONFLICT, exit 2, and names cotal space create <name> --adopt --root <root>, and status names the same command. --adopt takes the journal over: it rewrites it to name this folder, moves a broker store that lay under the recorded folder to the same place under this one, and records a stack that never ran here, so the next cotal up starts it from this folder with the copied store. It refuses with CONFLICT, writing nothing, when the folder's .cotal is a link, when the recorded folder still holds the journal, because this folder is then a copy of a stack that runs from there, when this machine records an instance of that stack, because the folder was then moved on this machine and moves back, and when the space is prepared or registered on this machine, where its Next names cotal space forget first. A copy brought from another machine and a folder moved after its record was forgotten are adopted. A folder renamed from a path that cotal up refuses, such as one with a tab in it, is told only to adopt, since moving it back would not start the stack. Without --adopt, nothing rewrites a journal.

With no --space, --server or --creds, nothing selected and no project in the folder, a command that several running spaces could serve asks which one when it runs in a terminal. The answer is the target of that one run, and the selection stays as it is; cancelling the question does nothing and exits 0. A script, a --json caller and --no-input are not asked: the command refuses with TARGET_AMBIGUOUS, exit 2, naming the running spaces. Every refusal of a target puts what to run next in error.remedy, apart from error.message, and names the space and root it concerns under target when they are known, as for a prepared space that is not started.

When this account cannot read or enter a project folder's .cotal/auth, for example because it cannot enter the folder's .cotal, the spaces the folder holds are unknown. A command that would target that folder, including through --server, refuses with DEPENDENCY_UNAVAILABLE, exit 2, and names the folder and the read error. When a folder on the way to .cotal/auth cannot be entered, the next step names that folder rather than .cotal/auth. The same refusal names each account record in .cotal/auth that will not load, such as an auth.json that is not JSON, and says to repair or remove it. It does not name running spaces or pick another space, and it changes no file. --space <name> still targets a space by name.

--creds <file> presents that credential to --server. Without --server, it goes to the broker its space is registered at and keeps that registration's TLS requirement. The space is --space, else the selected space, else the space of the project the command runs in, else main. Inside a project whose root has one space other than the selection, it is that space. A command never picks the only running space for a credential. A space not registered here is refused with TARGET_NOT_FOUND, exit 2, before anything is dialed. The refusal says whether it is the selected space, this folder's space or the default space, and a space other than the default is also under target. The default address is never assumed for a credential. The file is read and checked with the NATS client's own parser before anything is dialed, and the refusal names the file. A missing file is TARGET_NOT_FOUND, exit 2. A directory, an empty file and a file the client cannot parse are SCHEMA_INVALID, exit 1, with the cause: no complete JWT block, no seed block, an invalid seed, or Windows (CRLF) line endings. A file in a folder that another account can write, or below one, is refused PERMISSION_DENIED, exit 2, as auth mint refuses to write one there: the refusal names the folder, and its Next is one chmod, such as chmod go-w <dir>. A file that other accounts can read is still used, with a warning on stderr that gives the chmod 600 command. join --creds reads its file the same way. A credential the broker rejects is PERMISSION_DENIED, exit 2. When this machine holds the space's account and that account revokes the file's key, the refusal says when it was revoked with auth revoke-credential. Its Next is a mint with a new key, which for an agent file names the read and post channels and the role the file held. When the credential's own permissions name one other space, the refusal names that space, such as credentials rejected at <server>: <file> is a credential for space "<owner>", not "<space>", and its Next is --space <owner>. When --server named another broker than the one that space is registered at here, the Next says to use --space <owner> without --server, naming that broker. Keeping --server with --space <owner> refuses again, saying the space is registered at another address, and the Next is to drop --server or point it at that broker.

A manifest's space: is the target of native up -f and the new team check/boot/deploy routes. A --space that names another space is refused before anything is written. The old spawn -f manifest spelling no longer deploys; team deploy applies all-new live channel-only manifests or bounded static/open agent and mixed manifests. Reapply remains unsupported.

cotal meshes still lists, registers and drops meshes. space, space add, space create and space forget replace it, and its --json lines are the old shape. A bare meshes add on a terminal is still the guided form.

meshes

cotal meshes [--json]
cotal meshes add                      # guided, on a terminal
cotal meshes add <space> --server <url> [--root <dir>] [--mode auth|open|user] [--tls] [--allow-unencrypted-overlay] [--force]
cotal meshes add <space> --mode user (--user-auth-file <bundle.json> | --from <https url>)
cotal meshes rm <space> [<space> …] [--force]
cotal sync [--idp <auth base URL>] [--json|--plain]
cotal status [--json|--plain] [--all] [-v|--verbose] [--space <s>] [--server <url>]
cotal status --components [--space <s>] [--server <url>]

cotal meshes is a legacy command. It still runs, and cotal help meshes still prints its help, but cotal help no longer lists it and completion does not offer it. cotal space lists the spaces, space add <name> registers one that runs elsewhere, including a user-auth space with --file or --from, and space forget <name> drops a registration.

Everyday command forms use positional agent names: cotal attach <name> and cotal stop <name>. For one-shot messages, choose one destination with --to/-t, --channel/-c or --role/-r: cotal send -t reviewer "Please inspect the patch". Message receipts do not repeat the body. They distinguish storage from delivery and say that no reply listener remains active.

Bare cotal shows a compact list of useful actions. cotal help shows the same list, then every command by group. cotal help --all, the spelling from before help listed every command, prints the same. A hidden command, such as a deprecated daemon entry, is left out and still runs. cotal cfg is the shorthand for cotal config. Its default view shows effective values and sources; config --verbose adds the storage path and revision.

Status starts with This machine, lists the spaces this machine knows under Spaces, then shows the selected space, this folder's maintenance journal under Maintenance when it holds one (its state, attempt and next step; maintenance in JSON), unsettled fresh and additive attempts in this folder when present, owned service records and useful commands under Next. Connection state, local process state and service replies remain separate observations.

This machine shows what is set up and what needs attention. The version line is always there. Agent applications is one line: the applications whose required executables are on PATH, and a count of the others, which cotal setup adds. An application's own rows, such as the Claude plugin, Claude Desktop and Claude skills, appear for an application that is set up and only when they are not current. The broker executable appears when it is missing or when up refuses it. The dashboard appears when its record cannot be read. The extensions are one line, 11 extensions at 0.103.3, and an extension at another version is the only one named.

Spaces puts the space this report is for first, with its connection state, then one line per space: its name, its connection state and how it signs in. Only the space being reported is dialed, so the others carry no state and one note says that unselected spaces are not dialed and cotal status --all dials each. The broker address is not a group title here; cotal space show <name> names it. An account whose space catalog could not be refreshed is one line naming that account.

status --all and status --wide report every row in full: one row per known application, every extension with its version, the spaces grouped under their broker addresses with their origin, and the broker executable and dashboard rows. --all also dials every recorded broker; --wide dials no more than the default report. Use status --verbose for installation paths, extension records and the detailed local diagnostics. The default report does not include the agent roster; use ps or who. The deployment-attempt section lists at most 100 fresh and 100 additive local IDs, their returned/unsettled state and unreadable paths, with full readable coordinates in JSON. It appears only when there is something to inspect. A missing fresh summary does not prove the original broker never started; an additive intent does not prove which resources changed. This read grants neither retry nor stop authority. A fresh summary that names another folder, as one does after its folder was moved or copied, is listed with its recorded path and is not counted as unreadable. If the folder inherits the machine's home .cotal, status --verbose says that up there makes the folder its own project root. When the home holds a stack an earlier release started there, it also says that commands in the folder reach that stack until then.

A prepared space whose controller stack ran and was stopped reads as stopped, with its last operation and how it ended, such as `cotal down`, completed, and Next names cotal up in its root. A space that never started still reads as prepared but not started. A recorded file-store cap does not change the restart command because Cotal reserves no fixed capacity per space. meshes.currentStopped says whether the selection is such a stopped stack. A stack whose last up or down has no recorded outcome is not stopped, even when a member it launched still runs: the selection reads as prepared but not started, and the selected space names that operation.

Owned services use the saved controller definitions and instance records for the selected space. Instances are listed by definition name, then instance ID, in the text and in result.services.instances, so their order holds from one start to the next. Full instance IDs and execution hosts stay visible. Process observations and historical readiness are separate columns; neither a saved serving state nor a supervisor receipt becomes a fresh endpoint response or OS unit-state check. Legacy PID-file rows are labeled separately. On a root whose controller stack serves the selected space, its members are the stack- rows here, the legacy rows and the stored-state hint are omitted, and when the stack does not answer or a member's process is gone, Next offers cotal up --root <root> first, with <root> quoted when a shell would split it. On a stack whose last up was --no-manager it adds --no-manager, and a manager record an earlier up left does not count as a stopped member. When a service of the space outside the stack runs one of this root's records, such as a delivery preset started after cotal down stack-delivery, up refuses until it stops, so Next first names that service's svc stop with its instance, as the refusal does, and then up --root. In open or static-auth mode, an unreachable --server address other than the recorded one says nothing about whether the stack answers. Next offers up --root only for a member whose process is gone, and names the failed address the way other commands' TARGET_UNAVAILABLE refusal does. When an earlier down, or a foreground up whose terminal closed, ended after it signalled the broker or the delivery daemon and before it saw that process exit, up and down refuse until that stop is abandoned. status then says so under the selected space, names the member, the earlier stop as operation <id> and the member's stop as stop request <request-id>, and offers cotal down --abandon-stop <request-id> --yes with that stop request ID, which their refusal names too, and the member's service status, in place of up --root and of a step that names cotal up. Run in that folder while another space is selected, status says the same under This folder's space with the same Next first, and leaves out the folder note that names cotal status. A stack there whose last up or down has no recorded outcome and that is not blocked settles when it starts, so Next names the cotal up that starts it there. --json carries the same as folder.stackBlock. A manager stop is decided by the manager's own receipts, which only up and down read, so status does not name it. An up or down that was interrupted before it recorded an outcome, and left some members running and others stopped, is named under the selected space as Half started or Half stopped, with the members on each side. Next then offers the command that finishes it first: cotal up --root <root> for an up, with the root shell-quoted when needed and --no-manager kept when it was chosen, or cotal down --yes for a down, followed by cotal up --root <root> to restart instead. The restart root is also shell-quoted after an interrupted down or when recreating deleted space streams. --json carries it as folder.stackUnfinished, with kind, id, running and stopped. An up cut off before it registered the space leaves the selected space prepared and not started, and status names its stack's block or unfinished operation and the same Next under it. When that up left members running, the next up cannot finish it, so Next offers cotal down --yes and then cotal up --root <root>. With another space selected, status in that folder still reports this stack, under This folder's space, and so does every other command there while a member runs. While the stack is blocked, the Delivery responder row says that up refuses until the block is cleared. The next bare up or down settles the interrupted operation with an OPERATION_SETTLED warning. When the stack reached that operation's end anyway, every member running after an up or stopped after a down, the warning says it was interrupted and where the stack stands, without calling it failed. When that up, team boot or down ran to its end and exited 3 with its outcome unconfirmed, the warning says it ended with its outcome unconfirmed instead. The journal records a team boot's start as an up, so the warning calls either one a stack start. A running stack-manager gets one probe of its manager endpoint, and the selected space's Manager row reads answering, not answering · stack-manager pid <pid>, or endpoint not checked when the probe could not be made or another manager instance answered. A manager that does not answer puts kill -KILL <pid> first in Next, because cotal down cannot stop it; once that process is gone, cotal down --yes records it stopped. A folder this account cannot read is one warning that names the folder and chmod u+rx on it, and nothing in it is read. Unreadable service record families make the report partial instead of becoming empty lists. Warnings retain a classified cause code, such as unsupported host observation, without native diagnostic bodies. These reads import no provider and start, stop, adopt or repair nothing. --all extends broker checks; this local owned-service section retains its explicit selected-space scope.

Registry output

meshes lists the meshes this machine knows; (default) after a name marks the current default a bare cotal spawn joins. A terminal too narrow for its columns gets one labeled record per mesh, as other tables do. Entries learned from a signed-in account are marked discovered. Their registration trust is stored under the account's private auth state, and the registry contains no session token or sentinel credential bytes. Commands resolve the catalog slug; a different human name is rendered only as a label.

meshes --json prints one JSON object per recorded mesh per line: space, server, mode, root, default (the row marked (default)), and origin (up, manual, or catalog for a discovered entry). A local or hand-registered entry also carries offline. A discovered entry is never probed, so it has no offline field. tlsRequired, events: "required" and a discovered entry's catalogName appear only when the record has them. An empty registry prints nothing and exits 0. The note about a default that matches no record goes to stderr, so stdout carries only rows, on a first run too. The table is presentation and is not a stable parsing target. meshes add and meshes rm refuse --json.

Unreadable records

A registry record this build cannot use is refused by name, never rendered and never skipped. One that does not parse, or is missing a field every consumer reads (server, mode, root, ts, space), makes every command that reads the registry refuse with SCHEMA_INVALID, exit 1, before it acts. The refusal names the file under target without repeating its stored values; meshes also says what is wrong with it. status is the exception: it lists the readable records, names the file and exits 4. Remove the file or restore the record; nothing repairs or invents a field for you. A registry directory that exists but cannot be read, such as one without read permission or a regular file in its place, refuses the same way, naming the directory, and meshes gives the read error. Only an absent directory is an empty registry. The folder of prepared spaces, ~/.cotal/prepared, refuses the same way when it exists and cannot be read, on every command that reads it, such as space, space create, use <name> and up. A file in its place is named as <path> is a file, not a folder, with the step that moves it aside. A record in it that does not parse or is not a prepared space record refuses only a command about the space its file is named for, such as use <space>, and names cotal space forget <space>. Other commands skip it with the warning PREPARED_RECORD_SKIPPED, which names the file and cotal space forget <space>, the step that removes it, and status lists it under meshes.unreadable and exits 4. A record removed between listing and reading is omitted from that listing. A keyed read also treats a record removed during its read as absent; parse, shape and other read failures still refuse by name. Recording or removing a mesh also sweeps the directory for an older-format record of the same space. Any error listing the directory for that sweep, other than an absent directory, fails the operation with that error. An unknown --space names the missing registration and points to cotal space, which also lists prepared spaces.

Account catalogs

An IdP may advertise a same-origin space catalog during login. Cotal reads the complete snapshot and adds every valid registration without a separate meshes add. A snapshot younger than five seconds is used without a request. After that, commands that resolve a mesh target conditionally refresh the saved catalogs. An operation targeting a discovered space refreshes only that space's account and refuses if that account fails. Operations targeting local or manually registered meshes refresh every account and continue. They warn once when an account's catalog starts failing and stay quiet while it keeps failing; the next failure after a successful refresh warns again. cotal status refreshes every account, never refuses on a refresh failure, and lists each account as fresh, updated, not-modified, no-catalog, or failed with its error. With no cached sign-in, catalog preparation reads without creating a control directory or lock. An explicit IdP without a cached login refuses with AUTH_REQUIRED. An unreadable sign-in or catalog cache refuses with DEPENDENCY_UNAVAILABLE before refresh or registry application. Both exit 2 and leave the cache untouched; diagnostics name the path without printing cache contents or parser excerpts. cotal sync bypasses freshness and reports added, changed, removed, unchanged, name collisions, and the spaces the catalog marks unavailable. --idp limits it to one signed-in account. It never connects to a broker. --json gives each account under accounts with its idp, state, catalog, the six name lists, selectionCleared and error. An unavailable space keeps the registration this machine holds, which is also listed as unchanged; a command that targets it refuses with DEPENDENCY_UNAVAILABLE and the catalog's reason while its siblings keep working. An account whose catalog could not be refreshed keeps its last snapshot and is named in a CATALOG_UNAVAILABLE warning, so the result is partial, exit 4; when no account could be refreshed, sync refuses with DEPENDENCY_UNAVAILABLE, exit 2.

The registry is updated under the same lock that guards the catalog cache, so a command never lists a discovered space set that another command is still writing. The cache records a fetched snapshot as not yet applied before the first registry write and as applied after the last. If a command dies or is stopped in between, the next command applies that snapshot again before it can use it, with no request inside the freshness window.

The shared dispatcher applies this preparation to every command that declares both --space and --server as mesh-target flags, including commands registered by other packages and commands that declare their own equivalent flag objects. Daemon and startup commands that use those names only as configuration explicitly opt out. Registry-local meshes add and meshes rm never refresh a catalog. While the registry holds a record this build cannot use, the preparation neither refreshes nor applies a catalog, so the command's own checks run first. A snapshot left unapplied is applied by the next preparation after the record is restored or removed. A command that resolves its target through the registry still refuses the record by name.

Run on a terminal with the space or --server missing, meshes add is guided: it asks for the one thing that cannot be derived (the broker URL), probes it, and tells you what answered - open or requiring credentials. It then offers the spaces your --root already holds credentials for, and on an open broker the spaces that broker serves, states the mode as a fact about that broker rather than asking, and shows the exact record before writing anything. A broker that does not answer, or a space name already registered, becomes a choice rather than an error. When a broker answers at an address with TLS intent but this machine does not trust its certificate, the guided form says so with the flag form's handshake failure and its NODE_EXTRA_CA_CERTS step, and Cancel is the first choice, because Node reads that variable only when the command starts. An address the flag form refuses is refused with the same next step, such as the tls:// form of a host name, and the question that asks again shows no example address. For a name added by hand, the first choice is another name. Replacing a registered record then asks you to type y; Enter keeps No. Nothing is written before that last question, and a cancel at any question says nothing was registered. A name prepared or started on this machine, or listed by a catalog, is not offered for replacement. Anything you pass on the command line, --tls included, is taken as given and not asked again. Without a terminal - a script, an agent, CI - nothing prompts and the flag form's errors stand (COTAL_NO_PROMPT=1 forces that too).

cotal up and cotal down maintain their own records. meshes add registers a mesh they cannot speak for: one running on another machine, a shared broker, a hosted space. Like space add, it refuses a space name space create refuses, exit 1, with nothing written, even with --force. A mesh an earlier release registered under such a name still lists, shows and removes as before. --root is the folder whose .cotal/auth holds that mesh's credentials and whose .cotal/agents holds its personas. The default is the project you run it in. The registry stores that path, never a secret. --mode defaults to auth when the root holds the space's account record and to open otherwise. The broker is probed before anything is recorded, so a wrong address, or credentials that mesh will not accept, fails here instead of at the first spawn; --force records without verifying (and replaces a record added by hand). A name that is prepared on this machine, or that this machine started or a catalog lists, is refused with CONFLICT (exit 2), with or without --force, as space add refuses it: cotal down finds a running stack through that record.

Registration trust

A hostname or public address is registrable only when the connection will require TLS. Pass --tls, or use a tls:// URL. Without either, the refusal's next step is the tls:// form of the same address, or wss:// for a ws:// one. The scheme is recorded as enforced intent, so every later dial through the record demands the handshake (and meshes add tls://… against a plaintext broker is refused at registration). Without required TLS the fence admits loopback literals, and private-overlay literals only with --allow-unencrypted-overlay, which records your acceptance that the overlay is protected only while its tunnel is up (Run a mesh). RFC1918 addresses are refused in both modes because a cafe LAN is private but does not belong to you. --force does not change these address rules.

A user-auth mesh registers from supplied pinned trust, never guessed: --user-auth-file takes the bundle exported where the mesh runs; --from asks before it dials the address at all, then fetches the /.well-known/cotal-mesh discovery document under that address (HTTPS only; a URL that already ends in that path is fetched as given), displays the pins, and asks again before adopting them. The document must parse as JSON under any content type the host serves it with; an answer that does not is refused with the URL and the content type that came back. Neither fetch follows redirects: a 302 can move a pinned fetch onto plaintext or onto another host, so it is refused rather than followed, and the pinned exchange must itself be an https:// URL, except for an exchange on this machine, where plain http:// is accepted for a loopback literal (127.0.0.1, ::1, any spelling of them) but not for localhost, which is a name rather than an address. Registration verifies that the exchange answers /health and /jwks as the pinned issuer. It also verifies that the broker refuses a bare connect; that auth-required refusal is the pass. The sentinel credentials land in a 0600 file under the entry's root; the registry records only the path.

Registry ownership

meshes rm drops records. It never stops a mesh. For a mesh running on this machine cotal down is the right verb, and rm refuses and says so, with or without --force, because cotal down finds a running mesh by its record. A stack cotal up started stays a prepared space at its root after that down, which meshes rm does not list or remove, so the refusal names cotal space forget <space> for it. --force drops a record only when its folder cannot be checked for a running mesh. A broker record that cannot be proven stopped counts as running, as it does for clean. A hand-added record is removed by meshes rm, by an add --force replacement, or by a cotal up that actually starts the broker for that same space, server and root, which becomes that mesh and so takes the record over (a cotal up for that space anywhere else refuses instead).

For several names, meshes rm loads the registry once, shares the legacy-file scan, and reads the selected default once. Each completed entry is reported and its selected default cleared before the next entry is attempted. A later removal failure does not roll back earlier removals.

Nothing that merely infers a record is stale from a dead broker touches it: an unreachable broker is listed offline and stays, whether cotal up or cotal meshes add wrote the record; a foreground up whose broker exits unexpectedly keeps its record the same way. A broker that answers at a record's address and refuses the connection is read the same way while another registered space names that address: the broker there is that space's, so the record is kept and the refusal is CONFLICT, exit 2, naming the space that holds the address, the cotal space forget <space> that frees it and the cotal up --server <url> that moves this one. cotal status shows that space as address held. Deleting the record would leave its folder naming no space, after which nothing there says which space its commands meant. A bare command does not treat that offline record as a running mesh; name it with --space to restart it. An operator command whose target is the selected space, or the space registered at its project's root, probes no other recorded broker. When it picks among the running meshes instead, it probes the recorded brokers once, and its mode check and its connect both read that one result. Managed-seat control resolution and commands that inspect all spaces keep their existing probe behavior. cotal down / cotal clean all still drop an up record for the project they tear down; a hand-added one they leave alone even when it shares a root, because nothing on this machine could write it back.

A discovered entry belongs to the normalized IdP origin and proved subject that supplied it. Local teardown, cleanup, and liveness pruning do not remove it. A manual or locally started entry with the same name wins and remains untouched; that discovered name is reported as a collision. Logging out removes only the discovered entries owned by that account.

Required event planes

cotal meshes and cotal status print events: required for a registration carrying policy: { events: "required" }. On that space, foreground spawn, detached spawn and manager starts that host a native harness cannot opt out or launch without an event plane. --no-events is refused with the space named. A connector without an event plane is refused with both the space and connector named. A session whose own grant omits events.<owner>.<actor> is refused before joining and the message names a full-row actor grant repair. A running seat whose event plane stops keeps running and keeps its mesh connection on that space as on any other; the connector re-establishes the plane itself and reports every failure with the delay before the next attempt. A seat the manager resumes from a preserved cut meets the same rules as a fresh spawn: a seat retained with --no-events, or on a connector without an event plane, is refused, and a resumed seat carries the requirement in its launch material.

join, one-shot sends and passive protocol views host no native harness turns. They publish no AG-UI runs, regardless of who drives their input. Required events do not turn terminal messages into agent runs or copy channel and DM text into an event stream. This launch policy is not an additional broker authorization boundary. The broker still enforces each endpoint's grants.

The selection cotal use sets applies from every directory except the project folder of a running space, where the command targets that folder's space. There status reports that space under its Mesh: heading and a This folder's space section, and says the selected space applies outside the folder. --json names it in target.space and in meshes.folder, which cotal space --json gives as folder. In the folder of a space that is prepared or stopped, status reports the selection instead, and the line under its heading names what the folder holds and the cotal up that starts it; meshes.folder is then null. A folder whose stack is half started or half stopped, with a member still running, counts as a running space's folder here. In a copy or a moved folder, where the commands refuse under the space the folder's records name, status reports that space under its Mesh: heading, marks it in the Spaces list when it is registered, and says what the folder holds in a This folder section, with the step those commands name under Next. --json names that space in target.space and selected.space, and the recorded folder in selected.recordedRoot. status is a read-only report in this order: this machine (the installed cotal-ai version, NATS and each named integration), the installed extensions and their versions, this folder's .cotal/, the recorded meshes, the selected mesh (its connection, the delivery responder, its channels and membership feed), then the hints. It lists no roster; cotal ps lists managed seats. Stale Claude skills and out-of-date .agents skills recommend cotal setup --skills, not unscoped cotal setup. status takes --space / --server to pick the mesh to inspect; it starts nothing.

Broker probes

Status displays broker addresses without URL user information, query parameters or fragments, including component facts. Saved addresses and connection inputs are unchanged. An address that cannot be displayed safely is null in JSON, unavailable in text, and names a BROKER_ADDRESS_INVALID warning with a partial result. Its broker component is refused.

Only the selected mesh is checked. Its row under Spaces dials its address with its recorded TLS requirement, and every other row carries no connection state, under the note that says what was dialed. When --server names another address, that address is the only broker dialed, and the selected mesh's row carries no state either. In --json each of those rows is not-checked. --all checks every recorded mesh, including discovered ones, each with its own recorded TLS requirement. Each row then shows the check of its own address, the selected mesh's row included when --server names another.

JSON report

status --json writes one envelope whose target is {space, scope}, where space is the selected space or null and scope is selected or all. Its result is the report the text draws:

FieldHolds
kindinfrastructure
machineversion, install (kind and root), nats (path, bundled or missing), integrations (each {name, state, text} a connector's setup reports), connectors (each {name, requires, missing}), skills (state: current, not-installed, behind or unreadable), and web (installed, url and the error of each when it could not be read)
extensionsroot and items, each {package, label, version}
folderroot, auth (state: space, spaces, none or unreadable), personas (root, default: valid, invalid, missing or unreadable, demo and error), launches (whether a bare spawn launches from this folder's catalog; null when no mesh resolves), processes (each {name, state, pid, detail}, state one of running, stopped, not-serving, unknown or unreadable), storedState, noManager (whether this root's controller stack was started with --no-manager) and managerService (the service of the space, outside that stack, whose process runs this root's manager, as a manager preset started there does: its definition and instanceId, or null), heldServices (the services of the space, outside that stack, whose processes run this root's records, as up names them before it refuses: each definition, instanceId, record and pid; null when those records could not be read)
meshescurrent, folder (this folder's running registered space when a command there targets it, else null), unreadable (the registry directory, a record, the current-mesh pointer, or the prepared spaces folder or a record in it, that could not be read, or a pointer that is empty, each as {file, error}), items (each with space, server, mode, root, origin, tlsRequired, eventsRequired, current and check: reachable, unreachable or not-checked) and catalogs (each {idp, account, state, error}, where account is the IdP subject the error line names)
selected{resolved: false, code, message} when no mesh resolves, where message states why and hints carries the step (code is registry-unreadable when the registry directory or a record could not be read, and a not-started prepared space adds its space and root); otherwise the target's fields, connection (ok or the reason it failed; on a user-auth mesh reachable or unreachable), login, deliveryResponder (bound, unbound, stale or unknown, and null on an open mesh with no delivery lease), snapshot, channels, membershipFeed and spaceStreams. spaceStreams.state is present, absent or not-checked; it is read for an auth or user space and an open space this machine started. When absent, root is this machine's stack of the space and stack is running or stopped, each null when unknown. storedBytes is what the space's account holds in the broker's JetStream file store, null when it was not read
servicesSelected-space local owned records: space, state (read, partial or not-checked), non-atomic observedAt, public definition/instance projections and explicit scope. An unreadable family is null; stored readiness is historical. Older serialized snapshots may omit this observation, which is not an empty inventory.
stackManagerThe selected space's running stack manager: instanceId, pid and endpoint, which is answered, not-answering or unchecked. Absent when no stack manager runs
storeWhat the selected space's running local broker logged about its JetStream store: state write-failed with pid, log, at, cause, path and jetStreamDisabled, true when the broker then disabled JetStream; full with used, cap and path when the space holds more than its broker's recorded cap; damaged with pid, log, at and the broker's warning when it logged damage in a stored file; none-recorded with pid and log; or not-checked with a reason. Absent when no space resolves
hintsThe hints, in the order the report prints them

store covers only a broker whose log is on this machine: a controller stack's stack-broker instance, or the broker a legacy root records in .cotal/nats.pid with its log .cotal/nats.log. nats-server reports a stream's write error natively only through healthz, which needs a system-account user, so status reads a bounded tail of that log instead and keeps only lines tagged with the running broker's process ID since its last start. When the broker logged a store write failure, such as a full disk, the selected space shows a Store row with its time, cause and file. The broker keeps that error until it restarts, so messages sent meanwhile are not stored. The first Next is cotal down, to run once the logged cause is fixed: write access to the store again for a permission error, or free space for a full disk. Then cotal up starts the broker again. When the broker also disabled JetStream, which store.jetStreamDisabled shows, the manager's stop cannot be recorded, so the Next starts with kill -TERM <pid> for the stack's manager on this host, then cotal down --yes and cotal up.

When the broker logged damage in a stored file since it started, the Store row reads damage logged with its time and the broker's own words. The recognized warnings cover a message block whose records do not parse (indexCacheBuf corrupt), which the broker rebuilds without the messages from the damage on; a stream whose recovery rolled back past what a consumer had delivered; and a stream metafile that fails its checksum, which the broker does not load. It keeps serving what it can still read, so history holds only that. These warnings do not necessarily populate the STREAM.INFO lost-data field. For a controller stack the Next is cotal service logs stack-broker. status repairs nothing.

When the space holds more bytes than the --max-file-store cap this machine recorded for its broker, the broker refuses every write, so the Store row reads full with the bytes stored, the cap and the store's path. Its Next names the route up names, which keeps the history: kill -TERM <pid> for the stack's manager on this host, as a full store refuses its stop, then cotal down --yes and cotal up --max-file-store <bytes> with a larger cap. It does not name cotal history clear, which can leave the store over its cap. From another folder, the Next names the stack's folder to run cotal down --yes in, and up carries --root <dir>. The Delivery responder row then says that the full store refuses the daemon's lease, so up starts the daemon only once there is room. The bytes are the space account's own, as $JS.API.INFO reports them; the cap counts every account on the broker. A broker started without a cap sizes its store from the free disk, and status does not compare that.

deliveryResponder is stale when the ready row in the delivery lease belongs to a daemon that is gone. That includes this folder's own daemon once its recorded process has exited without releasing the row, as after a kill. The row expires on its own within 30 seconds. The delivery responder line then says the delivery daemon died and its lease has not expired yet, that spawn, retirement and the durable backstop wait until a new daemon binds while joins, DMs and live posts can still work, and that cotal up restarts it, since up waits out the lease first.

With no delivery daemon serving, as after cotal down stack-delivery, deliveryResponder is unbound. The delivery responder line says spawn, retirement and the durable backstop wait until a daemon binds, while joins, DMs and live posts can still work, and that cotal up starts it.

When a controller stack's delivery member runs but holds no delivery lease, deliveryResponder is unbound, and the delivery responder line names the member's pid and says it is starting or not responding. status cannot ask the daemon which. Next names cotal service status stack-delivery, then cotal down --yes, which kills a member that does not exit, and cotal up.

Partial reads

An unreachable broker, a stopped process or an unbound responder is a state the report names, and status exits 0. An open mesh runs no delivery daemon, so when it records no delivery lease its delivery responder row says the responder is not used and suggests no command. A controller stack started with --no-manager gets a Manager row saying it runs none by choice and that bare cotal up adds it. A part it cannot read is named on its row and as a warning, and the run is partial with exit 4: a PID record, the folder's trust material, the delivery-aware marker, the connectors, the skills bundle, the web records, a persona catalog, the mesh registry or one of its records, the current-mesh pointer, a user-auth login, or the selected mesh's channels or its owned service definition/instance records. Only an absent catalog, registry or pointer reads as empty or unset; an empty pointer is named too. A registry directory or record that cannot be read also leaves the selected mesh unresolved, because resolution refuses it, and the readable records are still listed. A corrupt or unreadable extensions manifest is refused while loading the command surface, before this report can run. The manager row asks the service endpoint once: a live process that does not answer is not serving, and a probe that could not be made leaves the row running · service unchecked. A broker that takes the connection and does not answer, as a stopped one does, leaves it unchecked too, and status still exits.

Local records

The local auth reader uses a stripped signer only when both split trust records are absent. The load refuses dangling and looping links at either record even when an older stripped signer is present. A process row whose PID record exists but cannot be read, or exists under both its current and a pre-upgrade name, reads pidfile unreadable with the error, and the other rows still print. A live manager whose delivery-aware marker cannot be read keeps its row and names the failure as delivery-aware marker unreadable with the error. The Web process row prints the address the selected mesh's dashboard recorded once it was listening, while the PID in its web.pid is alive. The dashboard that holds web.pid picks the record. A dashboard started with cotal service start writes its session record beside its service startup receipt, and status finds that record through the web service instance whose recorded process is that PID. Any other holder is read from web.session. Neither record is read in place of the other. A web.pid, session record or service instance record that exists but cannot be read is named on the row with the error. Otherwise it reads down, or not installed without the web extension. Status and the cotal setup card count the web extension as installed only when a package in the extensions manifest provides web and that package is on disk. A web package record that cannot be read, is not JSON or has no version is named on the Web extension row and on the card's web row, after the dashboard's address when it is listening and beside an unreadable web.pid or session record. The Web process row still prints, and reads down unless the dashboard is listening.

Account state

If a refresh fails, status may still show the kept catalog bytes for diagnosis. It labels them stale with the last successful snapshot timestamp and the refresh error. It never calls that state synchronized or online. If a selected discovered space vanishes from a successful snapshot, the selection is cleared and the command reports that no default is selected.

For a user-auth mesh the selected-mesh section reports the login status works as: the signed-in subject when this machine holds a cached session for the entry's pinned IdP, or the exact cotal login --idp <url> line when it does not, with no network round trip either way. A locally provisioned space also shows the actor grant row; a discovered or registered remote entry reports the grant as not checkable on this machine, because the ledger runs where the space was provisioned. --components on a user-mode target probes delivery as that same signed-in login, never a static mint. The broker row only checks that the registered address is reachable. It does not present the login. The manager status probe uses the manager-caller view cotal spawn already exchanges for that login. When this machine's manager process is live, the exchange is pinned to the instance id that process persisted. If that id is not known, or a different instance answers, the row is refused and is not reported as this process serving. An answer names the instance that gave it. A probe refused before any answer says so, and does not invent an instance. When this machine has no manager process, an unpinned answer is reported as serving and the row names that instance. A spawn holder carries status on the pinned instance. ps and slots are not part of this probe and stay with admin. When the login or that view cannot supply a credential, the manager row says why instead of printing the broker's refusal of an unauthenticated probe.

Persona rows name the catalog they describe. If this folder and the selected mesh use different catalogs, status names both and marks which one spawn launches from. A green default means the file passes the same agent-file loader spawn uses; a present but invalid file is reported as invalid.

cotal status --components prints the report, then a fail-loud per-component health pass. It takes no --json, --plain or --all. It reads each component's own control surface, rather than treating a PID, a lease, or a successful probe of a sibling as proof that the component serves. It prints one of serving, absent, not-serving, or refused for each component and exits 0, 1, 2, or 3 respectively (the highest observed state wins). An optional component that is not installed prints not-installed, which exits 0. That exit replaces the report's own, so a partial report followed by a clean pass exits 0:

  • manager: local PID record, its liveness-lease holder and PID, then the manager's own typed status service reachability from this host. A manager that answers without reporting static reconciliation says static reconciliation not reported by this manager build; the line stays visible even when the manager is otherwise serving. A row that reached no manager does not carry it.
  • delivery: local PID record, its ready lease (ready is the daemon's own bound-control signal), and the latest renewal.<spaceKey>.json adoption verdict, the record of the space the command was asked about, keyed per space the way the pidfiles are. A re-signed credential and a broker-accepted adoption stay distinct facts. A root-only renewal.json left by an older build names no space and is never read as any space's verdict (doctor auth names it as a leftover).
  • web: not-installed when the dashboard extension is not installed and no PID record exists. Otherwise, the local PID record, then the /api/meta response at the address the dashboard recorded once it was listening, which must name the same PID. The holder of web.pid picks the record, as on the Web process row: a service-run dashboard's own session record, found through its web service instance and named on the row as service instance <id>, or web.session for any other holder. The probe presents the readiness nonce recorded beside that address, the one credential the dashboard accepts on /api/meta. The probe follows no redirect. An answer with a status outside 2xx, a 3xx included, is not-serving, and the row names that status, such as HTTP 401. A live PID with no recorded address (the dashboard is still writing it, or an earlier build started it), or an unrecognizable process record, is refused, not a green default-port guess. A session record or service instance record that exists but cannot be read is also refused, and the row names the read error.
  • broker: the registered mesh URL dialed from this host with its recorded TLS requirement.

absent means Cotal has no live local component record (or has a stale record); not-serving means the component record is live but its service/readiness surface did not answer or is not ready. Those are intentionally separate exit cases. A failed or unreadable probe is refused, never an absent component or a clean zero. A broker that takes the connection and does not answer, as a stopped one does, refuses the manager and delivery probes, and the pass still exits. A PID record that exists but cannot be read, or exists under both its current and a pre-upgrade name, refuses only its own row. A record that its component removes while the pass runs reads as absent.

Each component prints on one line. A control character inside a reported reason, such as a newline in a stored row's field name, prints as its \uXXXX escape.

Registration

See the current command contract for this behavior.

Default selection

See the current command contract for this behavior.

Legacy registration

See the current command contract for this behavior.

status

See the current command contract for this behavior.

Report scope

See the current command contract for this behavior.

Component health

See the current command contract for this behavior.

On this page