main compiles and tests your agent, pushes a version, creates an agent version, and promotes it only after a reviewer approves. The same rules apply to scripts and coding agents: every command runs with complete flags and never waits on a prompt. For the release commands themselves, see Release an agent to production.
Verified against lua-cli 3.33.0.
Before you begin
- A scoped API key for CI, stored as a repository secret. The workflow below needs
agents:read,agents:write,automations:read, andautomations:write; addworkflows:readandworkflows:writewhen you push workflows, andtelephony:readandtelephony:writewhen the project defines a voice. Two steps may needknowledge:read, the scope that also returns production environment values: the closinglua logscheck, andlua testwhen the tool it runs readsDataorUser; grant it knowingly or drop them. Leave outagents:manage(it deletes versions) andknowledge:write.agents:writealso letslua push agentchange the served persona, model, model settings, and governance at once, with no promote. A scoped key cannot create or duplicate agents (lua init --agent-nameexits 10), so create the agent once from your own user session and commit itslua.skill.yaml. lua.skill.yamlcommitted and.envignored; production secrets live on the server (lua env production -k <KEY> -v <value>).- Node.js 16 or later on the runner; the workflow below uses 20.
1
Make every command non-interactive
Pass the global Without
--ci flag anywhere on the line. When a command would prompt, it fails instead with Interactive prompt required but --ci flag is set. Provide all required flags or arguments. and exit code 1, so supply the flags a prompt would have asked for: --name, --set-version, --force, -m, -e.--ci, a non-TTY runner only gets a warning and then hangs on the prompt. Some prompts are not intercepted by --ci and block instead; the full list is on the CLI overview. Of the commands this workflow runs: lua test skill without --name, lua logs without --type, --name, --user-id, or --json, and lua deploy <type> without --set-version or --force. lua integrations connect needs a browser for the provider’s authorization, so run it once from your machine; lua integrations webhooks create prompts for nothing when you pass --connection, --object, --event, and --hook-url; lua skills production <action> and lua persona sandbox <action> (other than view) open a menu and exit 1 under --ci, so script lua skills <action> and lua skills sandbox view instead.2
Authenticate with a secret
The CLI reads
LUA_API_KEY from the environment (a .env file in the working directory counts) before any user session or credentials file. With nothing set, every command exits 9 with No Lua CLI authentication found. Run `lua auth configure` or set LUA_API_KEY.lua status --json --ci reports auth.source (environment for the variable) and project.agentId, proving the runner targets the right agent. Set LUA_NO_HINTS=1 to drop the tip blocks the CLI prints after commands; the full list is on Environment variables.3
Branch on exit codes and JSON
Every command exits 0 on success and a class-specific code otherwise, printed as one stderr line Commands with
✖ <code>: <message>; the table is on Errors and exit codes. lua sync --check exits 1 when local code and the server differ, and lua compile --ci exits 1 on a compile error.--json print their result on stdout: lua status, lua logs, lua test, lua version list|show|diff, lua agents, lua models list, lua auth sessions, lua marketplace, and the lua workflows verbs. On failure, only lua workflows, lua auth sessions, and lua marketplace replace the typed line with { "success": false, "error": { "code", "statusCode", "message", "issues" } } on stdout; every other command keeps the ✖ line on stderr, so check the exit code before parsing stdout. lua push, lua deploy, and lua chat have no --json. Three outcomes exit 0 and must be read from the output instead: lua push all lists primitives that failed under ⚠️ <n> component(s) failed to push:, lua deploy all under ⚠️ <n> deployments failed:, and a tool that throws under lua test skill returns { status: 'error' }. lua sync --check also registers primitives the server has never seen (without a version).4
Add the GitHub Actions workflow
Three jobs: verify (compile, one tool test, refuse to release over a newer server version), stage (push and version create), and promote, which runs in a GitHub environment with required reviewers so the live swap waits for a human.
lua version promote itself asks no confirmation, so those reviewers are the only approval gate..github/workflows/release.yml
lua push all rewrites lua.skill.yaml on the runner; the next run derives versions from the server again, so it need not be committed back. Workflows are not part of lua push all; add lua push workflow --ci --force --name <workflow-name> and lua workflows deploy <workflow-name> -v latest to the stage and promote jobs when the project has any.5
Verify
Push to A version created with
main, approve the promote job in GitHub, then confirm from your machine that the active version carries the commit.Output
--commit-hash also carries commitHash in this output.Options you may need
Tie versions to git automatically
lua git connect makes lua push, lua version create|promote|delete, and lua pull create a commit (and a lua/v<n> tag on version create) in the project’s repository; --auto-push also pushes to a GitHub HTTPS origin after lua git auth github. See the lua git reference.
Ship one primitive from CI
Replace the stage and promote jobs with a single gated step when a CI job owns one webhook, job, processor, or trigger:lua push webhook --ci --force --name <webhook-name> followed by lua deploy webhook --ci --name <webhook-name> --set-version latest --force. For those types lua deploy creates and promotes a scoped agent version. A skill deploy is reset by the next lua version promote, so keep the version create and promote jobs for skills; a pushed persona needs no deploy (lua deploy reference).
If it isn’t working
✖ error: Interactive prompt required but --ci flag is set. Provide all required flags or arguments.
✖ error: Interactive prompt required but --ci flag is set. Provide all required flags or arguments.
Exit 1. The command needed a value you did not pass. For
lua push it is usually --name (more than one entity of that type) or --force; for lua deploy, the type or --name (its version picker and confirmation block instead of failing, so pass --set-version and --force). Each reference page lists the flag behind every prompt.✖ auth: No Lua CLI authentication found. Run lua auth configure or set LUA_API_KEY.
✖ auth: No Lua CLI authentication found. Run lua auth configure or set LUA_API_KEY.
Exit 9. The secret is not reaching the step. Set
LUA_API_KEY at the job or workflow level rather than in a single step, and confirm with lua status --json --ci that auth.source is environment.✖ forbidden: Access denied (403): …
✖ forbidden: Access denied (403): …
Exit 10. The key is valid but has no access to the agent in
lua.skill.yaml, for example a key from another organization. Run lua agents --json with the same key to list what it can see, and issue the key from the organization that owns the agent.Next steps
Release an agent to production
What each command in the CI job does and how to roll back.
Errors and exit codes
Every exit code and the
--json error envelope.Environment variables
LUA_API_KEY, LUA_NO_HINTS, LUA_DEBUG, and the rest.Claude Code plugin
The same gate for a coding agent working locally.

