opf/openproject · error · Setting::NotWritableError

#{name} is not writable but can be set through env vars or c

Error message

#{name} is not writable but can be set through env vars or configuration.yml file.

What it means

Setting#set_value! writes a setting value only when its definition declares it writable (or force: true is passed). Settings whose definition has writable: false — typically those fed from ENV variables or config/configuration.yml, such as database or delivery-related values — raise NotWritableError on assignment. The database copy stays authoritative only for writable settings; the others are provisioned externally.

Source

Thrown at app/models/setting.rb:122

  def nullable_integer_format?
    format == :integer && definition.default.nil?
  end

  def non_null_integer_format?
    format == :integer && !definition.default.nil?
  end

  def value
    self.class.deserialize(name, read_attribute(:value))
  end

  def value=(val)
    set_value! val
  end

  def set_value!(val, force: false)
    unless force || definition.writable?
      raise NotWritableError, "#{name} is not writable but can be set through env vars or configuration.yml file."
    end

    self[:value] = formatted_value(val)
  end

  def formatted_value(value)
    return value if value.blank?

    if definition.serialized?
      return value.to_yaml
    end

    value.to_s
  end

  # Returns the value of the setting named name
  def self.[](name)
    cached_or_default(name)

View on GitHub (pinned to d9742c43f3)

Solutions

  1. Set the value through its real source: the corresponding ENV variable or config/configuration.yml, then restart the application.
  2. Inspect the definition to confirm: Setting.definitions / the setting's writable? flag tells you which side owns it.
  3. Only if you truly must override in a migration/maintenance task, call set_value!(value, force: true) deliberately — normal code should never force.

Example fix

# before
Setting.foo = 'bar'   # raises NotWritableError

# after
# config/configuration.yml or ENV:
#   OPENPROJECT_FOO=bar
# then restart. Or, in a one-off maintenance task:
Setting.find_by!(name: 'foo').set_value!('bar', force: true)
Defensive patterns

Strategy: validation

Validate before calling

definition = Setting.definitions[name.to_sym]
raise Setting::NotWritableError, name unless definition.nil? || definition.writable?

Type guard

def writable_setting?(name)
  Setting.definitions[name.to_sym]&.writable?
end

Try / catch

begin
  Setting.foo = value
rescue Setting::NotWritableError
  instruct_env_configuration('OPENPROJECT_FOO')
end

Prevention

When it happens

Trigger: Calling Setting.some_setting = value (or setting settings via the API/console) for a setting defined with writable: false — e.g. after moving a mailer or storage setting behind an environment variable.

Common situations: Upgrades that convert formerly writable settings to env-var-managed ones while old deploy scripts/seeds keep assigning them; operators trying to change behavior from the admin UI or console without realizing the value comes from configuration.yml.

Related errors


AI-assisted analysis of opf/openproject@d9742c43f3 (2026-08-21). Data as JSON: /api/errors/d1c8a9a03b3abbbb. Report an issue: GitHub.