Serguey Asael Shinder: A configuration value nobody has ever changed is code with worse tooling
Somewhere in every mature service there is a settings file with forty entries, and about six of them have ever been set to anything other than their default. The rest were externalised at some point because a value that might need to change should not be hard-coded. It was a defensible decision each time, and collectively it has made the system harder to work with, not easier.
The trade is usually described as flexibility versus simplicity. That framing is too generous to the flexible side, because the flexibility is hypothetical and the costs are immediate:
The compiler stops helping. A constant of the wrong type does not build. A configuration value of the wrong type builds, deploys, and fails on the code path that first reads it — which, for a timeout or a batch size, might be the path taken once a day.
Find-usages stops working. Rename a constant and the tooling finds every reference. Rename a configuration key and you are grepping a string across YAML, environment variables, the deployment templates, the documentation and someone's shell history.
Reasoning about the value becomes reasoning about the deployment. "What is the timeout here" turns into "what is the timeout here, in this environment, after the overrides, given the precedence rules of the configuration library". That question has an answer, but it takes minutes rather than seconds, and it can only be answered for one environment at a time.

And it is a supported interface now. Once a key exists, someone will set it — in an environment you do not know about, to a value you never tested. Removing it later is a breaking change with no compiler error to warn you.
The rule I use is narrow enough to apply without an argument:
Externalise a value when there is a concrete, named reason for two environments to hold different values for it today. Not when you can imagine one.
Connection strings, credentials, feature flags with a removal date, anything that genuinely differs between staging and production: externalise. A retry count, a buffer size, a threshold that has been 0.8 since 2021: make it a constant with a comment saying where the number came from. The comment is the part people skip, and it is the part that makes the constant safe to change later — which was the whole point of externalising it in the first place.
The test for an existing key is easier than the test for a new one. Look at what it has actually been set to across every environment for the last year. If the answer is one value everywhere, it is not configuration. It is a constant with a longer feedback loop.