Fixing a self-hosted app's broken credential by updating it straight in the database feels like a perfectly reasonable shortcut — until the column you just wrote to turns out to be encrypted at the application layer, not just at rest. The write succeeds. The breakage shows up somewhere else entirely, later, looking nothing like what actually caused it.
A self-hosted app had a stale integration token stored in its own Postgres database. The real fix (a fresh,
valid token, confirmed working via a direct API call) was correct. Writing it into the app's database via a
plain UPDATE ... SET token_value = '<new value>' is where it went wrong — that column
turned out to be encrypted at the application layer (a common pattern: SQLAlchemy-Utils' EncryptedType
and equivalents in other frameworks), not just protected by disk-level encryption. The app always expects to
decrypt whatever's sitting in that column using its own installation-specific key. A raw plaintext value
written directly in breaks that expectation completely — but silently, at write time. Nothing errors on the
UPDATE itself.
The generalizable lesson: "the page shows nothing" is not proof that data was lost. Before assuming a deletion or a data-loss event, check the application's own backend logs for a decrypt or deserialization error on read. A silently-broken decrypt looks identical to missing data from the outside, but the fix (and the actual cause) are completely different.
Without the app's own encryption key — typically generated once per installation and not something you can reasonably recover after the fact (checked environment variables, config files, and install scripts; none of them tend to expose it, by design) — the corrupted value generally can't be repaired in place. Two real options once this has happened:
ON DELETE SET NULL relationship makes this safe) and re-enter the
entire affected configuration fresh through the app's own UI, not SQL.Before trusting what a self-hosted app claims is wrong with itself — especially anything licensing- or auth-shaped — check basic resource health (disk space, whether its own database process is actually up) first. Plenty of apps surface a generic or misleading error message for what's really just a resource exhaustion problem underneath.
Before writing directly to any column via raw SQL on a self-hosted app's database, check whether that
specific field is encrypted at the application layer — this is common for API tokens, passwords, and other
credential-shaped fields specifically. A quick way to check: look for framework-specific markers
(EncryptedType and similar are common in Python/Ruby ORMs) in the app's source or in error
tracebacks if something's already gone wrong. If a field is app-encrypted, go through the application's own
UI or API to change it, every time — raw SQL stays safe for plain, non-encrypted columns on the same table,
but treat anything credential-shaped as guilty until proven otherwise.