Skip to content

monoceros check

When an agent builds in your workbench, a few of its mistakes are invisible unless you go looking: a new project that never shows up in the editor, app files one directory too high, a pipeline compose file with a database version and credentials someone wrote from memory, a server on a port nothing can reach. monoceros check looks for exactly those, so you do not have to know what to look for.

Terminal window
monoceros check <name>

It reads the materialized workbench on your host. No container has to be running, nothing is started, and nothing is changed: it reports and stops there. The exit code is 1 when it found something and 0 when it did not, so you can put it in a script.

Terminal window
monoceros check acme

A clean workbench says so, and names what it looked at:

▸ Briefing check: acme
✓ Nothing to report. Checked 2 projects, 1 compose file, 1 launch config.

Otherwise you get the findings, grouped, each with the fix:

▸ Briefing check: acme
Workspace registration
⚠ projects/shop
Not listed in acme.code-workspace, so it does not show up in the editor.
→ Add { "path": "projects/shop", "name": "shop" } to the `folders` array.
Launch config
⚠ projects/shop/.monoceros/launch.json → web
Port 4200 is not exposed on the container, so the proxy cannot reach it.
→ Run `monoceros add-port acme 4200` and `monoceros apply acme`, or move the target to an exposed port.
2 findings in 2 projects, 0 compose files, 1 launch config. Nothing was changed.

Workspace registration. Every directory directly under projects/ needs an entry in <name>.code-workspace, or the editor you open from the host simply does not list it. Repos that Monoceros cloned are registered for you; a project an agent scaffolded is not, unless the agent did it.

Files at the workspace root. The root of the workspace holds Monoceros-managed directories, not code. Anything else there is a project file that landed one level too high and belongs under projects/<app>/.

Compose drift. A project compose file whose service block differs from the block Monoceros handed the agent in .monoceros/deploy.md: a different image tag, a missing healthcheck, or a value that no longer fails fast where the block requires ${VAR:?…}. Your own variable names are fine there, ${PG_PASSWORD:?…} passes; what gets reported is a variable without the :?, which starts the service on an empty value when the pipeline forgets the secret, and a literal value, which keeps the credential in the repo. Only service keys that name a catalog service are compared, and the finding names the difference: if your project runs an older database on purpose, that is your call to make.

Launch config. A project that clearly serves something (a dev script, Django’s manage.py, a Spring Boot build) but declares no launch config, a target on a port the workbench does not expose, and a start command that pins the server to 127.0.0.1, where the proxy cannot reach it.

Three more things about a target are decidable before you ever start it. A cwd that does not exist under the app directory. A package script the project does not define, the npm run dev where the script is actually called start, which otherwise surfaces as an npm error out of the container on your first monoceros start; the finding lists the scripts the package does have. And a readyTimeout the pinned runtime is too old to honour, where the field is dropped in silence and the target keeps the 20 seconds it was written to escape. Commands that cannot be resolved without guessing are left alone: a compound command, a workspace flag pointing at another package, and everything outside npm, pnpm and yarn.

Service config nothing mounts. A file written for a service at the location its docs prescribe, projects/<app>/keycloak/realm.json for example, that no volume in the yml feeds to it. The service then runs and never reads it. This one is not carelessness: an agent working inside the container can write the realm, but the mount lives in the yml on your host, where it cannot reach. The finding reads the file to say what it is and hands you the volume line to paste.

Ports. Two launch targets on the same port, where only one of them can bind it, and a port the yml exposes that no launch config declares, so the route answers nothing. And the one that answers 502 Bad Gateway for a container that is otherwise healthy: the routes exist, the server inside listens, but the container is not on the proxy network, so nothing can reach it. That one is reported with the two ways out, and only for a container that is running, because a stopped one joins the network on its next apply anyway.

Git identity. A container with no user.name or user.email, where the first commit fails with “Please tell me who you are”. Nothing else shows it: the container starts, git runs, and the failure lands halfway through whatever you were doing, often inside an agent that was told to commit its work. The fix is one line in monoceros-config.env: GIT_USER_NAME and GIT_USER_EMAIL there cover every workbench you build from then on.

Briefing markers. An AGENTS.md or CLAUDE.md that lost its monoceros:begin / monoceros:end pair. The next apply then rewrites the file whole, and your own notes in it are gone without a word.

monoceros status carries the two rules about whether a running thing can actually answer, right on the row they concern: a target on an unexposed port, and a service whose config file nothing mounts. Both print the way out there, so you rarely need to reach for check to fix them. The other rules stay out of status: a compose file that pins an older database on purpose is your call, not something to be nagged about on every status.

Two rules leave no trace to check. Whether the repo is written in English, and whether service configuration was copied from the catalog block instead of written from memory, are things only a reader can tell. That is why they sit in the first lines of the briefing rather than in more text further down.