Safely set GitHub secrets with gh secret set
You will create or update a GitHub secret from a Linux shell without printing its value, then verify that the secret exists at the intended scope. Allow about ten minutes if you already have GitHub CLI authentication and permission to manage the target. The installed command here is GitHub CLI 2.87.3, so check the version on your own machine before relying on details in a script.
The route
Jump straight to the step you need, or tick off Done means at the end.
- 1. Check the command and your target
- 2. Choose the secret scope
- 3. Enter one value without exposing it
- 4. Supply a value from a protected shell variable
- 5. Load several secrets from a dotenv file
- 6. Set organisation visibility deliberately
- 7. Verify the name and scope
- 8. Test encryption without storing a secret
- Recovery and removal
1. Check the command and your target
Start in the repository that should receive the secret, or decide which repository to name explicitly. This command changes remote GitHub state, so do not use a real secret while testing. You do not need root privileges, and using sudo would not grant GitHub permission.
$ gh --version
gh version 2.87.3 (2026-02-23)
$ gh secret set --help
Create or update secrets
$ gh auth status
The exact build date and authentication output vary. The useful checkpoint is that gh secret set --help shows the command and that gh auth status reports an account and host you recognise. If authentication fails, fix that first. Do not put a token into a shell command merely to make this check pass.
2. Choose the secret scope
With no scope flag, gh secret set targets the current repository. Use --repo OWNER/REPO to remove any doubt about the destination. An environment secret needs --env ENVIRONMENT as well as a repository. An organisation secret uses --org ORGANISATION; a user secret uses --user and is for Codespaces.
The application also matters for some secrets. The installed manual accepts actions, codespaces and dependabot with --app. Do not add an application flag just because a workflow happens to use a secret. Use it when the secret is intended for that application.
# Repository secret in the current repository
gh secret set DEPLOY_TOKEN
# Repository secret in an explicitly named repository
gh secret set DEPLOY_TOKEN --repo OWNER/REPOSITORY
# Environment secret
gh secret set DEPLOY_TOKEN --repo OWNER/REPOSITORY --env production
# Organisation secret for Dependabot
gh secret set REGISTRY_TOKEN --org ORGANISATION --app dependabot
These examples would prompt for a value and could change state. Treat them as command shapes only until you have chosen the real name and scope.
3. Enter one value without exposing it
The simplest interactive form is the safest starting point: omit --body and enter the value when prompted. Keep the terminal private and check that shell history does not contain the value. The command locally encrypts the secret before sending it to GitHub.
$ gh secret set DEPLOY_TOKEN --repo OWNER/REPOSITORY
? Paste your secret: ********
✓ Secret DEPLOY_TOKEN set
Prompt text and success symbols can differ between releases. A successful exit status is the reliable result. If you are updating an existing secret, the new value replaces the old value. There is no version or undo operation supplied by this command, so retain a controlled recovery value only if your security policy permits it.
4. Supply a value from a protected shell variable
Use --body when another process has already provided a value, or read it silently from the terminal first. The variable is still present in the shell environment while the command runs, so do not export it broadly or leave it in a shared session.
$ read -r -s SECRET_VALUE
$ printf '\n'
$ gh secret set DEPLOY_TOKEN --repo OWNER/REPOSITORY --body "$SECRET_VALUE"
$ unset SECRET_VALUE
Quote the expansion. An unquoted value can be split by the shell or expanded unexpectedly. Avoid --body 'real-secret-value': command lines can be captured by history, process inspection or logs. If a CI system provides a masked variable, pass that variable according to the CI system's secret-handling rules and ensure debug tracing is disabled.
5. Load several secrets from a dotenv file
For several values, the command accepts a dotenv-formatted file with --env-file. A file containing secrets is sensitive: restrict its permissions, keep it out of version control and remove or securely retire it according to your retention policy after the import. Do not paste a real file into a ticket or chat transcript.
$ umask 077
$ touch /tmp/github-secrets.env
$ chmod 600 /tmp/github-secrets.env
$ editor /tmp/github-secrets.env
$ gh secret set --env-file /tmp/github-secrets.env --repo OWNER/REPOSITORY
$ rm /tmp/github-secrets.env
Populate the file in the dotenv format expected by the command, with the actual secret names and values. Review the target and file path before running the import. The final rm is destructive and cannot recover a file; use it only after confirming that you no longer need the local copy. Never put the file in a repository merely to make the import convenient.
6. Set organisation visibility deliberately
Organisation secrets have an access policy in addition to their name and value. The installed command defaults --visibility to private. Use all for all repositories, or selected with --repos for a selected list. --no-repos-selected creates an organisation secret that no repositories can access, which can be useful while preparing a value but is not a usable workflow configuration.
# Selected repositories only
gh secret set REGISTRY_TOKEN --org ORGANISATION --visibility selected --repos repository-one,repository-two
# Both public and private repositories
gh secret set REGISTRY_TOKEN --org ORGANISATION --visibility all
Visibility changes the set of repositories that can use the secret. Review the organisation name, application and repository list before pressing Enter. A secret value is not made safe by narrowing access after an accidental broad upload, so choose the policy before storing it.
7. Verify the name and scope
Secret values cannot be read back. Verify the metadata at the same scope instead, using gh secret list. It should show the secret name and an updated timestamp or equivalent metadata, but never expect the plaintext value in the output.
$ gh secret list --repo OWNER/REPOSITORY
$ gh secret list --repo OWNER/REPOSITORY --env production
$ gh secret list --org ORGANISATION
$ gh secret list --user
Use the first command for a repository secret, the second for an environment secret, and the later commands for their matching scopes. If the name is absent, check the repository, environment, organisation, user and application flags before trying again. A workflow may still fail if its job cannot access that scope, if the environment requires approval, or if the secret name in the workflow differs.
8. Test encryption without storing a secret
When you need to inspect the encrypted representation without changing GitHub, use --no-store. It prints an encrypted, base64-encoded value instead of storing it. This is diagnostic output, not a replacement for a configured GitHub secret, and it is still sensitive enough to keep out of logs and tickets.
$ gh secret set TEST_VALUE --no-store
? Paste your secret: ********
BASE64_ENCRYPTED_VALUE
Do not confuse successful encryption with a successful workflow configuration. The normal command stores the value; --no-store deliberately does not.
Recovery and removal
If you set the wrong value but still need the secret, run the same scoped command again with the corrected value. If the secret must not exist, removal is irreversible from the CLI's point of view, so confirm the exact scope first:
$ gh secret list --repo OWNER/REPOSITORY
$ gh secret delete DEPLOY_TOKEN --repo OWNER/REPOSITORY
For an environment, organisation or user secret, repeat the matching --env, --org or --user option. If a value may have been exposed in a command line, file, log or terminal recording, rotate it at the service that issued it as well as updating GitHub. Deleting the GitHub entry does not revoke the underlying credential.
Done means
- You confirmed the installed GitHub CLI version and authenticated account.
- You selected the intended repository, environment, organisation or user scope.
- The value was entered interactively or passed through a protected variable, not written into command history.
- Any dotenv file was permission-restricted, excluded from version control and retired safely.
gh secret listshows the expected name at the matching scope.- You know how to replace or revoke the underlying credential if it was exposed.