Build1 distinct publisher3 min readUpdated
Enrolling a spare security key everywhere leaves SSH guessing which credential to offer first. The fix is an explicit ~/.ssh/config with IdentitiesOnly yes, and it has one sharp edge.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A developer running two YubiKeys, one on a keyring and one in a drawer, both enrolled everywhere that matters, spent months watching `git push` ask for a PIN, reject it, and then ask again [1][2]. The variable was not the network or the repo but which of the two tokens happened to be in the USB port, and the root cause was an absence: no `~/.ssh/config` at all [2][5].
Without a config, ssh offers the agent's keys in the order the agent hands them over, and the server takes the first one it recognises [5]. Both credentials were `sk-ssh-ed25519` keys loaded automatically into the gnome-keyring agent, listed as `yk1-12345678` and `yk2-12345679` [4]. GitHub had both enrolled, so it accepted the first offer every time, which means ssh committed to yk1 before anything had checked whether the attached token could satisfy that commitment [6]. With yk1 in the port the guess is right and you get one prompt; with yk2 in the port, ssh asks the plugged-in token to sign for a credential it has never held, so you get a dialog, type your PIN, watch it fail, and then succeed on the fallback [7].
The reason this is visible rather than silent is a flag. Both keys are resident credentials with user verification required, and the flags byte in the private key blob is `0x25`, which decodes with the constant names from OpenSSH's `sk-api.h` as `SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD | SSH_SK_RESIDENT_KEY` [8]. Before ssh can request an assertion at all, the client must obtain a PIN/UV auth token from the authenticator, and that is the step that puts the dialog on screen; only after the PIN is typed does the assertion request go down the wire and the token answer that it has never seen this credential [9]. Strip `USER_VERIFICATION_REQD` and the wrong-key attempt fails quietly, which is why most people with two enrolled tokens never learn the ordering exists [10].
The tax is small and constant: five seconds, about fifteen times a day [3], which works out to roughly 75 seconds a day and around five hours over a 250-day working year [17].
The fix uses `Match exec`, which runs a shell command and applies the block if it exits zero [12]. Two blocks, each testing for a serial number and pinning one `IdentityFile` plus `IdentitiesOnly yes`, resolve it [12]. The serial mapping was never missing: the author had already written each token's serial into the key comment when generating it, so `ssh-add -l` had been printing the answer for months [11].
Two details decide whether this helps or hurts. First, the `host` criterion is not optional. `Match exec` on its own applies to every destination, and the first matching block then pins `IdentitiesOnly` and a single identity for whatever you are connecting to, so every other host with every other key stops authenticating [13]. Second, `IdentitiesOnly yes` is the load-bearing line and the one people omit. It does not mean "ignore the agent"; `-v` shows the agent still returning both keys. It restricts which of them are eligible to be offered, to those whose public half matches an `IdentityFile` in scope [14].
Watch the fresh-machine case. Because these are resident credentials, you can arrive on a new laptop, pull the keys off the token with `ssh-add -K`, and have nothing in `~/.ssh` at all, at which point the blocks match nothing and do nothing [15]. If neither serial matches, no block applies and you fall back to trying everything, which is the failure mode you want from a config that does not understand its situation [16].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The author carries two YubiKeys, one on a keyring and one in a drawer, both enrolled everywhere that matters, on the reasoning that a hardware token with no backup is a deliberately chosen single point of failure.
For months, git push sometimes asked for a PIN and worked, and other times asked for the PIN, failed, and asked again, with the second attempt working; the variable turned out to be which of the two keys was in the USB port.
The author describes the problem as a five-second annoyance hit roughly fifteen times a day.
Both keys are sk-ssh-ed25519 credentials loaded automatically into the gnome-keyring agent, and ssh-add -l lists them as 256-bit ED25519-SK keys with comments yk1-12345678 and yk2-12345679.
The author had no ~/.ssh/config at all; without one, ssh offers the agent's keys in the order the agent hands them over and the server takes the first one it recognises.
GitHub has both credentials enrolled, so it accepts the first offer every time, meaning ssh commits to yk1 before anything has checked whether the token in the port can satisfy it.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Self-consistent single-machine account with checkable mechanics
The technical chain is unusually checkable for a personal post: pasted ssh-add -l output, a flags byte decoded against named OpenSSH sk-api.h constants, a stated ssh -v observation that the agent still returns both keys, and a verbatim config snippet. But it is one author, one laptop, one agent (gnome-keyring), with placeholder serials and no independent reproduction, version numbers, or before/after measurement, which caps how far the evidence can carry.
No adoption signal in supplied material
The source reports one developer's personal configuration change. There is no release, deployment, usage disclosure, benchmark, pricing or licensing event, and no indication that anyone other than the author has applied the Match exec pattern, so adoption cannot be measured from the supplied material.
Slightly understated relative to what is shown
The framing is deliberately small — a five-second papercut, an author who feels 'slightly silly' — while the material actually documents a general failure mode for anyone following backup-key advice, and flags a config edit that can break authentication to every host if the host criterion is omitted. Caveats (fallback behaviour, both-keys-attached coin flip, empty ~/.ssh on a fresh machine, cost of calling ykman on every ssh invocation) are stated rather than buried, and no product or vendor is promoted, so the small negative reflects modest scope framing rather than any overselling.
Low commercial pressure, engagement-driven platform
A first-person post on dev.to with no product, sponsorship, or vendor affiliation disclosed and nothing being sold; the named tools (OpenSSH, gnome-keyring, ykman, GitHub, GitLab) are incidental to the debugging story and one of them is criticised for not exposing a serial cheaply. The residual incentive is the ordinary one of a developer-platform post competing for reader attention, which favours a tidy narrative arc — problem, self-deprecating reveal, fix.
Moderate: mechanism plausible and specific, single unreplicated source
Confidence is limited chiefly by cluster structure — one publisher, one first-person account, no adoption evidence — rather than by internal weakness. The mechanism claims are specific enough to be checked by any reader with two enrolled security keys, and the sequencing explanation (PIN/UV auth token before assertion) coheres with the observed symptom, which supports moderate rather than low confidence.
build
Stop trying to remember your Git email: includeIf makes identity a property of the directory1 distinct publisher
build
AI-written code fails the same four ways, and every gate you own reports green1 distinct publisher
build
Grok 4.6 lands in Copilot two days after launch, and the model picker becomes a procurement problem1 distinct publisher
build
GitLab bundles a zero-click GraphQL flaw with a CSRF bug, and only one needs a victim1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 20, 2026