Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Bash & Python Automation By James Joyner IV · · 7 min read Last reviewed Jul 2026

Bash Error Guide: 'readonly variable' — Fix Assignments to Locked Vars

Quick answer

Fix Bash 'readonly variable': stop reassigning a variable declared readonly, rename colliding names, and avoid re-sourcing files that redeclare constants.

  • #bash
  • #automation
  • #troubleshooting
  • #errors
Free toolkit

Stuck on this Bash & Python Automation error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

Overview

Bash prints readonly variable when a script tries to assign to a variable that was declared readonly (or declare -r):

./deploy.sh: line 22: VERSION: readonly variable

A readonly variable is a deliberate constant — once set, Bash refuses any later assignment and, under set -e, may abort the script. The error means code somewhere tried to change a value that was locked. Common triggers are re-sourcing a file that declares constants twice, a name collision with a shell-reserved or already-frozen variable, or an honest attempt to mutate a value meant to be fixed.

Symptoms

  • An assignment to a specific name fails while every other assignment works.
  • The failure appears the second time a config file is sourced, not the first.
  • Reusing a common name (SECONDS, UID, BASHPID, PPID) as your own variable fails.
  • Under set -euo pipefail the script exits at the assignment line with this message.

Common Root Causes

  • Re-sourcing a constants filesource config.sh runs twice (e.g. once from a parent, once from a child), and its readonly VERSION=... runs again on the now-frozen name.
  • Assigning to a shell-maintained readonly — names like UID, EUID, PPID, BASHPID are readonly by the shell.
  • A name collision — your variable shares a name with one another sourced file already froze.
  • declare -r / readonly earlier in the same script, then later code tries to update it.
  • Exported constants inherited from the environment as readonly (rare, but possible via a parent shell).

Diagnostic Workflow

List which variables are currently readonly:

readonly -p | grep -i version    # is VERSION frozen, and to what value?

Find where the name is declared readonly:

grep -rn 'readonly\|declare -r' . | grep VERSION

Trace to see the double-source or the offending reassignment:

bash -x ./deploy.sh 2>&1 | grep -n 'VERSION='

Confirm whether a file is being sourced more than once by adding a guard probe:

echo "sourcing config from ${BASH_SOURCE[1]:-?}" >&2

Example Root Cause Analysis

A deploy pipeline failed intermittently:

deploy.sh: line 22: RELEASE_ID: readonly variable

The constants lived in env.sh:

# env.sh
readonly RELEASE_ID="$(date +%Y%m%d%H%M)"

deploy.sh sourced env.sh, then called helpers.sh, which also sourced env.sh. The second source re-ran readonly RELEASE_ID=... on the already-frozen name, and set -e aborted. The fix is an include guard so the file’s body runs at most once:

# env.sh
[[ -n "${__ENV_SH_LOADED:-}" ]] && return
__ENV_SH_LOADED=1

readonly RELEASE_ID="${RELEASE_ID:-$(date +%Y%m%d%H%M)}"

The return short-circuits repeat sources, and defaulting with ${RELEASE_ID:-...} makes re-declaration idempotent even if the guard is bypassed.

Prevention Best Practices

  • Add include guards to sourced files — a __LOADED sentinel with return prevents double-declaration of constants.
  • Make readonly declarations idempotentreadonly X="${X:-default}" so a second run doesn’t fight the first.
  • Avoid shell-reserved names — don’t use UID, PPID, SECONDS, BASHPID, etc. for your own variables.
  • Reserve readonly for true constants — don’t freeze values you later need to update.
  • Namespace your variables — prefix project constants (APP_VERSION) to dodge collisions with other sourced files.
  • Run ShellCheck, which flags assignments to known-readonly and reserved variables.

Quick Command Reference

readonly -p                     # list all readonly variables and values
readonly -p | grep NAME         # check if a specific name is frozen
grep -rn 'readonly\|declare -r' .   # find where constants are declared
# idempotent constant:
readonly APP_VERSION="${APP_VERSION:-1.0.0}"
# include guard at top of a sourced file:
[[ -n "${__LIB_LOADED:-}" ]] && return; __LIB_LOADED=1

Conclusion

readonly variable is Bash protecting a constant from being reassigned. The usual cause isn’t a logic bug but a file sourced twice re-running its readonly declarations, or a collision with a shell-reserved name. Add include guards to sourced files, make constant declarations idempotent with ${X:-default}, and namespace your variables. Then constants stay constant without aborting the script on the second load.

Free download · 368-page PDF

Fixed it? Get 500 Bash & Python Automation & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.