hashicorp/terraform · error

The -upgrade flag conflicts with -lockfile=readonly.

Error message

The -upgrade flag conflicts with -lockfile=readonly.

What it means

Thrown by terraform init when both -upgrade and -lockfile=readonly are specified simultaneously. These flags are mutually exclusive because -upgrade fetches newer provider versions (which requires modifying the dependency lock file) while -lockfile=readonly forbids any lock file changes. The check runs in the arguments package before any init work begins.

Solutions

  1. Remove -lockfile=readonly if you need to upgrade providers
  2. Remove -upgrade if you need a read-only lock file
  3. Split into two pipeline stages: first run init -upgrade to update providers and lock file, then subsequent runs can use -lockfile=readonly

Example fix

# before
terraform init -upgrade -lockfile=readonly

# after (choose one depending on intent)
terraform init -upgrade
# or
terraform init -lockfile=readonly
Defensive patterns

Strategy: validation

Validate before calling

// Validate init flags before invoking terraform init
func validateInitFlags(upgrade bool, lockfile string) error {
    if upgrade && lockfile == "readonly" {
        return errors.New("-upgrade conflicts with -lockfile=readonly: " +
            "upgrading providers requires writing to the lock file")
    }
    return nil
}

Prevention

When it happens

Trigger: Running terraform init -upgrade -lockfile=readonly. The arguments package detects init.Upgrade==true and init.Lockfile=="readonly" and appends this error to diagnostics.

Common situations: Copy-pasting a CI pipeline command that includes both flags; misunderstanding that upgrading providers inherently requires writing to the lock file; attempting to enforce lock file immutability while also wanting provider upgrades.

Related errors


AI-assisted analysis of hashicorp/terraform@d32a084675 (2026-08-11). Data as JSON: /api/errors/bd20aa47fb617979. Report an issue: GitHub.

Appendix: source

Thrown at internal/command/arguments/init.go:220

		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"The -migrate-state and -json options are mutually-exclusive",
			"Terraform cannot ask for interactive approval when -json is set. To use the -migrate-state option, disable the -json option.",
		))
	}

	if init.MigrateState && init.Reconfigure {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"Invalid init options",
			"The -migrate-state and -reconfigure options are mutually-exclusive.",
		))
	}

	if init.Upgrade && init.Lockfile == "readonly" {
		// This is appended as a Go error because this validation already existed this way
		// and it's been moved earlier in the process, to the arguments package.
		diags = diags.Append(fmt.Errorf("The -upgrade flag conflicts with -lockfile=readonly."))
	}

	args := cmdFlags.Args()
	if len(args) != 0 {
		// No positional arguments are expected.
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,
			"No positional arguments are expected",
			"The init command does not expect any positional arguments. Did you mean to use -chdir?",
		))
	}

	backendFlagSet := FlagIsSet(cmdFlags, "backend")
	cloudFlagSet := FlagIsSet(cmdFlags, "cloud")

	if backendFlagSet && cloudFlagSet {
		diags = diags.Append(tfdiags.Sourceless(
			tfdiags.Error,

View on GitHub (pinned to d32a084675)