Troubleshooting
Diagnose Grain authentication, workspace links, remote drift, static-runtime errors, desktop handoff and continuous sync failures.
Start Grain troubleshooting with the doctor output, then match the failure to installation, authentication, client registration, workspace selection, file synchronization, runtime type or desktop handoff. The recovery commands below preserve local work unless a force or discard option explicitly says otherwise. For HTTP API failures, see the API error and credential guide; for agent registration, see Integrations.
Collect the current state
grain doctor [path]
grain statusThe doctor command accepts one optional path and no flags. It prints cli, auth, api, session, token expiry, link and remoteRoot lines. Status has no flags and reports login, resolved workspace, local root and sandbox state. Doctor rejects flags with Unknown doctor option: <option> and multiple paths with Usage: grain doctor [path]. Status rejects arguments with Unknown status option: <option>.
The grain command is missing
The production CLI is bundled in the Grain desktop app. Rerunning the one-line installer repairs the grain link, or open Settings → Integrations, copy the Grain CLI commands and run them in a terminal, then start a new shell. Check the managed wrapper and PATH without changing credentials:
ls -l "$HOME/.local/bin/grain"
export PATH="$HOME/.local/bin:$PATH"
command -v grain
grain --versionIf the app moved, repeat CLI setup so the managed wrapper points at the current bundle. The native plugin uses the same wrapper; reinstalling the plugin is not an installation repair.
Authentication is missing
grain loginLogin has no flags. It hands authentication to the desktop app and stores revocable CLI authentication. If any option is supplied, it reports Unknown login option: <option>. Start the desktop app, complete sign-in and rerun the original command. MCP registration does not authenticate the CLI, and API mat_/mfd_ tokens are not interchangeable with the CLI session.
The agent shows no Grain tools
- Run
grain init --dry-runin the project root and inspect which recognized configuration file would be written. - Confirm the entry starts the local
grain mcpcommand and has no stalemorphname or oldgrain_sharetool assumption. - Run
grain login, restart the agent session and ask it to list tools. - If a native plugin is installed, run the vendor’s list/details command from Integrations, then start a fresh session. Remove only duplicate user-level registrations; project entries are separate.
A config file proves only that the client knows how to launch a stdio process. A successful private artifact result with a workspace URL and confirmed save fields proves the full authenticated path. The hosted server in Remote MCP is a separate connection with its own sign-in.
Remote drift blocks a push
grain pull [--force]
grain push [--force] [--no-reload] [--dry-run]- When push says
The remote workspace changed since this folder was last synchronized., do not force immediately. Pull first, review the resulting local files and preserve the edits you want. - Pull has one flag,
--force. Normal output reports remote changes pulled, local paths deleted and local changes preserved. When another client holds the workspace lease, pull identifies the active sync client. - Push accepts
--force,--no-reloadand--dry-run. A successful retry printspushed <n> path(s). Use--dry-runto see proposed paths; use--no-reloadwhen an open workspace should not refresh. - Use push
--forceonly when you intentionally overwrite remote drift. The failure action explicitly recommends pull, review and retry first.
A static workspace has no compute runtime
grain upgrade [--target react-vite|nextjs]Upgrade accepts --target react-vite|nextjs and an optional idempotency key. It promotes a clean, pushed HTML workspace to an app runtime and does not update the CLI. Invalid targets return a usage error. Local edits return This checkout has local changes that are not in the active managed-content revision. Push them first. Remote drift requires a pull and review. Poll the returned operation until runtimeReady: true; a pending or cleanup state is not usable. A workspace that already has a runtime reports This workspace already has an app runtime.
The desktop app does not open or refresh
grain open
grain refresh [path]Open has no flags and reports that the desktop app was asked to open the selected workspace id. Refresh accepts one optional path and no flags, then reports that the app was asked to refresh previews for that workspace. When the desktop handoff is unavailable, both direct you to start the app and retry. Extra arguments return Usage: grain open or Usage: grain refresh [path].
Continuous sync does not start
grain start [path] [--foreground] [--restart] [--no-pull] [--continue|--abort] [--no-open] [--debounce-ms <n>] [--push-interval-ms <n>]
grain stop [path]Start accepts --foreground, --restart, --no-pull, --continue, --abort, --no-open, --debounce-ms and --push-interval-ms. Healthy output reports the path watched by continuous sync. Use --restart when a stale daemon must be replaced. --continue and --abort are mutually exclusive; negative debounce and nonpositive interval values are rejected. Checkout-mode folders report Continuous sync cannot run from a checkout folder. Use explicit push and pull there. Stop accepts one optional path and no flags; checkout mode reports A checkout folder does not run the continuous sync daemon.
A checkout cannot be released
grain release [--dry-run]
grain checkout list
grain checkout remove <workspace-id> [--discard] [--dry-run]Release accepts --dry-run and removes only a clean, validated checkout. Expected output is Removed checkout <id> at <path> or its Would remove preview. The checkout list subcommand has no flags. Checkout remove accepts --discard and --dry-run. If it reports Checkout <id> has unpushed changes., push the checkout or rerun with --discard only when those changes are intentionally abandoned. Repeating a dry-run flag reports Use --dry-run only once.
An option or value is rejected
- Read the usage text in the error. Command flags belong after that command; global
--jsonand--workspace <id>belong before it. - When the message says
Missing value for <flag>, provide a value that does not begin with another option marker. - Do not combine mutually exclusive choices such as start continue and abort, or public and password sharing.
- Use each command's dry-run option at most once. Dry runs report proposed state without writing it.
A runtime job is pending or unknown
Use grain job status <operation-id> rather than starting a duplicate. Jobs are bounded and local to the current CLI profile. If a disconnected worker is unknown, inspect the runtime and stop any remaining command before using grain job acknowledge <operation-id> --remote-stopped. Pull and review runtime-authored files before checkpointing.
The HTTP API rejects a request
Confirm the origin is https://api.rungrain.com, not the website origin. Use a Firebase ID token for token-management calls, a workspace-bound mat_ token for SQL/commands, and an mfd_ token for Front Door shell/files. A CLI mca_ session token is not a public API credential. Check the JSON error.code, workspace ID, scope and content type; do not retry a non-idempotent command blindly after an uncertain response.
For Front Door, call capabilities first, send argv rather than a shell string, use application/octet-stream for uploads, and keep file paths relative to the workspace. A command that exits nonzero can still be HTTP 200; inspect exitCode and stderr.
Frequently asked questions
Why does Grain say the remote workspace changed?
Another client changed the remote state after your folder last synchronized. Pull, review the local result and push again.
Why can I not run a command in a static workspace?
Static HTML workspaces have no compute runtime. Push a clean checkout and promote it only when the artifact needs an app runtime.
Why does the Grain app not open from the CLI?
The desktop handoff is unavailable. Start the desktop app and retry open or refresh.
Can I delete a checkout with unpushed changes?
The safe commands refuse by default. Push wanted work first; use discard only when you deliberately want to abandon the local changes.