Install difyctl in the Job
Use the installer, and always set DIFY_VERSION to your server’s version, so a new Dify release doesn’t change the build the job gets. If the installer stops at GitHub’s rate limit, set GITHUB_TOKEN or GH_TOKEN in the job’s environment.
Sign In
For a job that runs unattended, sign in yourself and pass it the token. When someone can approve a code each time, a script can sign in itself.Pass a Token to the Job
Get the token with an account used only for CI, or on a machine you don’t otherwise rundifyctl on. The account needs the Editor role or above to import, test, and publish. If the same account signs in again on that machine, the job’s sign-in ends.
Use --no-keyring, so you can copy the token from a file:
tokens.yml in the config folder:
DIFY_SERVER: the address you signed in with.DIFY_TOKEN: the token, from the secret.DIFY_WORKSPACE_ID: the workspace’sid, whichdifyctl get workspacelists.
DIFY_CONFIG_DIR to a folder it can.
Then delete tokens.yml, so the token lives only in the CI secret. This signs out the machine you signed in from, while the job keeps working. Running logout instead would end the job’s sign-in too.
Renew the Sign-In
When the job’s commands fail withauth_expired (exit code 4), sign in again and replace the token in the CI secret.
The sign-in stops working when:
- It expires: after 14 days by default, or the number of days
OAUTH_TTL_DAYSsets on your server. - The same account signs in again on that machine, even in another config folder.
difyctl get account session in the job and check expires_at on the session whose device_label is the machine you signed in from.
Sign In from a Script
Start the sign-in with--no-wait. Like any login, it first signs out the current session, then prints the code and exits without waiting:
verification_uri and user_code to someone who can open the URL and approve the code within 15 minutes. Until you finish the sign-in, don’t start another login: it discards this one, so approving its code no longer signs you in.
Then finish the sign-in in the same config folder:
- Until the code is approved, it prints
{"status":"pending"}and exits with 0, so checkstatusrather than the exit code. - Once the code is approved, it prints the signed-in
accountinstead, with nostatus. - If the code is denied or expires first, it fails with exit code 4. Start over with
login --no-wait.
Handle Failures
Run each step in bash withset -eo pipefail.
These commands exit 0 even when the job should stop. Check their output with jq -e, which fails when its test is false:
check console_app dsl, when the file is invalid:jq -e '.valid'import console_app dsl, when the import is left pending for someone to confirm:jq -e '.status | startswith("completed")'check console_app dependency, when the workspace lacks integrations the app uses:jq -e '.leaked_dependencies | length == 0'test console_app workflow, when the run paused or was cut off:jq -e '.status == "ended"'check console_app release, when the draft isn’t ready to publish:jq -e '.ready'
Example: Update an App on Merge
This GitHub Actions workflow checks the app’s DSL file,apps/order-status.yml, against the app when a pull request changes it. On merge to main, it updates and publishes the app. For what each step does, see Update an App.
To use it, in the repository’s Settings > Secrets and variables > Actions, add the secret DIFY_TOKEN and the variable DIFY_WORKSPACE_ID. Then replace the example values with your own: the server address, the app’s ID and name (difyctl get console_app lists both), the file path, the Dify version, and the test inputs.
.github/workflows/update-order-status.yml