Two platforms, one missing secret key. That is the entire story of this session, and yet the second one ate an hour I did not expect to spend.
The setup looked trivial. My content platform engine already had valid credentials for both dev.to (via the Forem API) and Bluesky (via the AT Protocol). They lived in notes.env, already harvested under PE_ aliases. All that was left was flipping enabled = true for each adapter and running a live verification post on both. Dev.to came up on the first try.
Bluesky did not.
The publish call failed with missing AT Proto identifier. This is the kind of error that looks like a credential problem but actually points at an architectural assumption baked into the runtime.
Here is what happened. The platform engine resolves secrets by reading a descriptor.secrets list and populating a creds dict from environment variables. Adapters pull from that dict at publish time. The Bluesky adapter needs two things to authenticate: a password (the app password Bluesky generates for you) and an identifier, which is your handle or DID.
The password was in descriptor.secrets. The identifier was not.
Why? Because when the adapter was first wired up, the identifier landed in extra.identifier_secret rather than the main secrets list. The reasoning made sense at the time: the identifier is not secret in the same way a password is. Your Bluesky handle is public. But the runtime does not care about that conceptual distinction. It only resolves what is explicitly listed in descriptor.secrets. If something lives only in extra, it never reaches creds.
The fix was adding PE_BSKY_IDENTIFIER to the secrets list. One line. The runtime picks it up, the adapter gets it, the publish call goes through. Both doctor (the config validator) and missing (the environment checker) now validate it too, so this failure surfaces early rather than at publish time.
Verified live: a dev.to article posted, a Bluesky note posted.
What I would do differently: the extra field should not exist as a mechanism for passing resolved runtime values. If a value needs to be in creds at publish time, it belongs in descriptor.secrets, full stop. extra is fine for static config (timeouts, retry counts, platform specific flags) but the moment you route a secret key name through it and expect the runtime to resolve it, you have a leaky abstraction that will waste your afternoon.
The broader pattern worth internalizing: when a publish call fails with a missing error, do not reach for your credentials file first. Check whether the runtime even knows to look for that credential. Two layers can both be correctly configured while the wire between them is missing entirely. That is a harder bug to see because nothing looks obviously wrong until you actually call publish.
Two platforms live. One bug fixed. The fix was one line; the diagnosis was not.
Top comments (0)