Docs ▸ Agents
For AI agents
- Add
--jsonto any command: stdout is exactly one JSON object,{ ok, data, errors[], warnings[], logPath }.logPathis the run's log (forplay,build,checkandpublish). Without a validgameshipper.jsonthey write nothing into the folder, so aCONFIG_MISSINGorCONFIG_INVALIDresult has nologPath. - Every error and warning has a stable
codeand a plain-Englishmessage. Errors always have afix; warnings have one whenever there's something to do. - Nothing ever prompts when
--jsonor--yesis set, or when stdin isn't a terminal. Missing input comes back asINPUT_REQUIRED, naming the flag to pass. - Exit codes:
0success,1a target or check failed,2invalid config, usage or missing input, or a login or token you must provide. A signal ends the run with anINTERRUPTEDresult and exit code 128 + the signal number (130 for Ctrl+C). Ctrl+C during an interactiveplayjust ends the session: anokresult with aPLAY_STOPPEDwarning, still exit 130. help --jsonreturns every command and flag.check --jsonreturns 18 checks, each with a stableid, astatus(pass,warn,fail,infoorskip) and, forwarnandfail, acodeand afix. Every failed check is also an error inerrors, and every warning inwarnings, so exit code 1 means at least one check failed. Opendata.deckScreenshotto see what the game showed at Steam Deck size.play --screenshot 5,10,15,20,25 --jsonsaves store screenshots without a person;data.screenshotslists them. They show whatever the game shows without input, so look at them before you upload them.publish --jsonnever prompts. Without a saved Steam or itch.io login it returnsSTEAM_LOGIN_REQUIREDorITCH_LOGIN_REQUIRED(exit code 2): the human runs the command in the error'sfixonce in their own terminal, then you publish again.npx gameshipper mcpserves the same commands as MCP tools, with the same results (see MCP server and Claude Code plugin).
The loop: status --json, init --yes --json, then play --smoke --json until it comes back clean (ok: true, so no errors; warnings don't block), then build --json, then check --json until nothing fails that you can fix. When the human wants it uploaded: publish --dry-run --json, then publish --json.
What play --smoke reports
| Code | Level | What happened |
|---|---|---|
GAME_CONSOLE_ERROR |
error | The game logged an error or threw, with the file and line. |
GAME_FILE_MISSING |
error | The game requested a file that isn't in the build. |
GAME_PATH_CASE |
warning | Comes with GAME_FILE_MISSING when the file exists with different letter case (Player.png vs player.png). Builds treat those as different files on every OS, so the request fails everywhere. The fix names the real file. |
GAME_LOAD_FAILED |
error | The page failed to load, or never finished loading. |
GAME_CRASHED |
error | The game's process crashed. |
SMOKE_NO_RESULT, SMOKE_TIMEOUT |
error | The game never reported back, or froze and had to be stopped. |
GAME_USES_INTERNET |
warning | The game loaded something from the internet while running, so it won't work offline. |
GAME_CONSOLE_WARNING |
warning | The game logged a warning. |
STEAM_INIT_FAILED |
error | Steam is running but didn't start for the game's App ID (usually one the signed-in account doesn't own). |
STEAM_NOT_RUNNING |
warning | Steam isn't running, so window.steam was a no-op. |
STEAM_OVERLAY_FAILED |
warning | The native overlay window (see Steam) couldn't start, so Shift+Tab shows no overlay. |
PLUGIN_LOAD_FAILED |
error | A plugin's module failed to load in the game. |
SCREENSHOT_FAILED |
warning | A screenshot couldn't be saved. With --screenshot it's also an error when fewer were saved than asked for. |
GAME_NAVIGATION_BLOCKED |
warning | The game tried to take its window to a page that isn't the game's, like about:blank or file:, or an embedded site tried to open a link. Web, mail and steam: links the game itself opens aren't reported: they open in the player's browser or app. But while the page has an embedded frame from another site, or one that failed to load, the game's own window.open() and target="_blank" links are blocked too, because Electron can't tell which frame opened a window. |
Each code lists at most 10 issues, then one more issue counting the rest. data.smoke.summary.events holds the first 100 diagnostic events; data.smoke.eventsLog names a JSON-lines file with all of them, including everything the game logged.
Other problems have codes too, such as CDN_FETCH_FAILED, CDN_CHANGED, ENTRY_MISSING, CONFIG_INVALID, ICON_TOO_SMALL, ICON_NOT_SQUARE, STEAM_SDK_MISSING, STEAM_SDK_INVALID, STEAM_SDK_CACHE_FAILED (Gameshipper's cache can't be written), STEAM_NATIVE_DOWNLOAD_FAILED (another OS's native Steam bridge couldn't be downloaded), STEAM_UNSUPPORTED_PLATFORM (a warning: Steam has no libraries for that OS and CPU, like arm64 Linux, so it runs without Steam), PLUGIN_LOAD_FAILED, PLUGIN_IMPORTS_NOT_COPIED (a warning, see Plugins), PLUGIN_HOOK_FAILED and PACKAGE_FAILED from a build, or GAME_EXITED when a game quits with an error during play, each with its fix. An icon problem only stops build: play warns with the same code and uses the default icon.
What check reports
| Check | Not met | Code |
|---|---|---|
offline.requests |
fail | CHECK_OFFLINE |
launch.run |
fail (warn when this computer's OS isn't a target) | CHECK_NOT_LAUNCHED, CHECK_LAUNCH_FAILED |
launch.errors |
warn | CHECK_GAME_ERRORS |
launch.frame |
fail (warn when the frame is mostly one colour) | CHECK_BLANK_FRAME, CHECK_MOSTLY_BLANK_FRAME |
launch.exit |
fail | CHECK_EXIT_CODE |
launch.other-os |
info | |
deck.layout |
warn (skip when the page couldn't be laid out at 1280x800) | CHECK_DECK_LAYOUT |
deck.gamepad |
warn | CHECK_NO_GAMEPAD |
deck.text-input |
always passes; lists text fields | |
review.builds |
fail | CHECK_BUILD_MISSING |
review.steam-appid |
fail | CHECK_STEAM_APPID_FILE |
review.mac-signing |
fail on a Mac, warn elsewhere | CHECK_MAC_NOT_NOTARIZED, CHECK_MAC_SIGNING_UNKNOWN |
review.icon |
warn | CHECK_DEFAULT_ICON |
review.app-id |
warn | CHECK_TEST_APP_ID |
store.images |
fail (also when older than the key art or logo) | CHECK_STORE_IMAGES |
store.screenshots |
fail | CHECK_SCREENSHOTS |
store.trailer, ai.disclosure |
info |
A check that couldn't run here is skip, and its message says why.