Operational patterns for inspecting and managing Alex's (and exxir's) Railway-hosted projects from the VPS.
Exxir/jungle-website) so Hermes can read/learn from itvps-app-deployment which is self-hosted at apps.poofc.comInstall (Node global):
npm i -g @railway/cli
which railway && railway --version # expect: /home/avalon/.npm-global/bin/railway, 4.59+
This is the #1 footgun. The CLI accepts BOTH names but they mean different things:
| Env var | Token type | What it works with |
|---|---|---|
RAILWAY_TOKEN |
Project token (created inside one project's Settings → Tokens) | Scoped to one project, fails for multi-project listing |
RAILWAY_API_TOKEN |
Account token (created from user settings → Tokens, "no workspace" = all workspaces) | Works for whoami, list, cross-workspace ops |
Symptom of mixing them up: railway whoami returns Unauthorized. Please check that your RAILWAY_TOKEN is valid... even though the token works fine — because the token is an account token and the CLI looked for a project token under RAILWAY_TOKEN.
Fix: export as RAILWAY_API_TOKEN for account tokens. Verify with:
RAILWAY_API_TOKEN=xxx railway whoami # → Logged in as <email>
RAILWAY_API_TOKEN=xxx railway list # → workspaces + projects
Save to ~/.hermes/.env so all subsequent shells pick it up:
echo "RAILWAY_API_TOKEN=YOUR_TOKEN_HERE" >> ~/.hermes/.env
chmod 600 ~/.hermes/.env
If railway whoami still fails after that, the wrapping shell may not have sourced the env — pass it inline once: RAILWAY_API_TOKEN=$(grep RAILWAY_API_TOKEN ~/.hermes/.env | cut -d= -f2) railway whoami.
railway link is interactive but can be driven with flags:
mkdir -p /tmp/railway-PROJECT && cd /tmp/railway-PROJECT
railway link --project "Exxir App" # picks workspace + project + env (production)
railway status # shows linked project + ID + env ID
A project with multiple services requires --service NAME for service-scoped ops:
railway service Sync\\ QB # NOTE: railway uses display names with spaces
railway logs --service "jungle-website"
railway list only shows project names. For service-level detail (URLs, last deploy, status, linked repo) use the GraphQL API directly — it's faster and works without railway link:
RAILWAY_API_TOKEN=xxx
PROJECT_ID=$(railway status --json 2>/dev/null | jq -r .projectId) # or hardcode from `railway status`
curl -s -X POST https://backboard.railway.com/graphql/v2 \
-H "Authorization: Bearer $RAILWAY_API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"query\":\"query { project(id: \\\"$PROJECT_ID\\\") { services { edges { node { name serviceInstances { edges { node { source { repo image } domains { serviceDomains { domain } customDomains { domain } } latestDeployment { status createdAt staticUrl } } } } } } } } }\"}"
Key field gotchas in the schema:
- Service itself does NOT have a source field — query serviceInstances.edges.node.source { repo image } instead
- Same for domains and latestDeployment — they live under serviceInstances
- A repo: null, image: null source means the service was deployed via Caddyfile/Dockerfile drop or empty template, NOT GitHub-linked
Use this query to disambiguate "which one is live" when you see e.g. jungle-website, jungle-website-booking-preview, Sync: Jungle in the same project:
- Sort by latestDeployment.createdAt desc
- A FAILED deployment is NOT the source of truth even if it's the most recent attempt
- Check customDomains for the canonical production hostname (e.g. junglestudio.com) vs the railway preview URL
When source.repo is set (e.g. Exxir/jungle-website), clone via gh — Alex has collaborator access on exxir repos:
cd /home/avalon
gh repo view Exxir/jungle-website --json defaultBranchRef,pushedAt,description,url
git clone https://github.com/Exxir/jungle-website.git jungle-website-ref
If the clone partially fails with No space left on device, see the disk-cleanup pitfall in vps-app-deployment. After freeing space, finish the checkout with git restore --source=HEAD :/ from inside the clone.
RAILWAY_TOKEN works for railway login --browserless/PAT flow but NOT for account tokens — always use RAILWAY_API_TOKEN for headless/account use cases.railway list is just project names — service/deployment detail requires GraphQL or railway status --service NAME.Service.source does not exist in GraphQL — drill into serviceInstances.edges.node.source. Same for domains and latestDeployment.FAILED, the previous SUCCESS deploy is still serving production. Check both status and createdAt.source: { repo: null, image: null } is not GitHub-linked — it was deployed via Caddyfile/Dockerfile drop or template. You can still inspect it via Railway but you won't find the code on GitHub for that service.railway service NAME requires the display name with spaces — Sync: Jungle not sync-jungle. Quote it or escape spaces.~/.hermes/.env or pass inline; never echo it back to the user in chat.railway whoami returns the expected email (firemountain@gmail.com for Alex's account token)railway list shows expected workspaces (exxir's Projects + My Projects for the full-access token)curl -sI https://DOMAIN/gh repo view OWNER/REPO confirms collaborator access before clone