The web console's outstanding-pairings view and its approve modal showed "This device" for every knocking client. The console is faithful — it renders PendingDevice.name exactly as the host received it, and the host really was told "This device".
Root cause
The name rides Hello::name, which embedders fill from client::device_name(). That resolves COMPUTERNAME (Windows-only) then HOSTNAME (a shell variable, never exported into a launchd-started GUI process) and last-resorts to the literal "This device". No Apple GUI app has either variable, and the C ABI's connect had no device-name parameter for an embedder to pass a better answer through — so every Mac, iPad, iPhone and Apple TV knocked as the placeholder, and a console with three of them pending showed three identical rows. The Apple client already knew a good name (UIDevice.current.name / Host.localizedName, used in the PIN sheet); it had no way to send it on the connect path that produces a pending knock.
Linux (/etc/hostname) and Windows were unaffected. Android passed Build.MODEL on its own JNI path — no placeholder, but the product name, so two identical tablets were still indistinguishable.
Changes
core: punktfunk_connect_ex10 = connect_ex9 + device_name (C ABI 20 → 21, no wire change — ex9 keeps its signature and passes a null name, which selects the old default). Names are truncated to HELLO_NAME_MAX on a character boundary (slicing a multi-byte name mid-scalar panics); covered by a unit test. device_name() now falls back to gethostname() (.local stripped, localhost rejected) before the placeholder.
apple: new DeviceName.current (Host.localizedName on macOS, UIDevice.current.name on iOS/tvOS, hostname fallback for the 16+ model-only answer) is sent on every connect; the two pairing sheets and the ceremony fallback now read the same source instead of three separate literals.
android: Settings.Global.DEVICE_NAME — the name the user typed in Settings — ahead of Build.MODEL. The "approve this device" prompt quotes the same string the connect actually knocks with.
web: the approve dialog names the device and its fingerprint in its description. A pre-filled field is editable text, not a statement of which knock is being approved — the fingerprint is the only thing that stays unique when two devices share a name.
Existing paired devices keep their stored trust-store names; only new knocks change. The host and console binaries are behavior-unchanged apart from the dialog copy — clients must be rebuilt to send their real names.
Apple: fresh 5-slice xcframework; swift build reaches "Build complete!" on macOS, iOS and tvOS
Android: :app:compileDebugKotlin BUILD SUCCESSFUL (3 ABIs clean at the API-28 floor)
web: codegen + build + tsc --noEmit clean
Not verified on-glass — needs a rebuilt client knocking at a host.
The web console's outstanding-pairings view and its approve modal showed "This device" for every knocking client. The console is faithful — it renders `PendingDevice.name` exactly as the host received it, and the host really was told "This device".
## Root cause
The name rides `Hello::name`, which embedders fill from `client::device_name()`. That resolves `COMPUTERNAME` (Windows-only) then `HOSTNAME` (a shell variable, never exported into a launchd-started GUI process) and last-resorts to the literal `"This device"`. No Apple GUI app has either variable, and the C ABI's connect had **no device-name parameter** for an embedder to pass a better answer through — so every Mac, iPad, iPhone and Apple TV knocked as the placeholder, and a console with three of them pending showed three identical rows. The Apple client already *knew* a good name (`UIDevice.current.name` / `Host.localizedName`, used in the PIN sheet); it had no way to send it on the connect path that produces a pending knock.
Linux (`/etc/hostname`) and Windows were unaffected. Android passed `Build.MODEL` on its own JNI path — no placeholder, but the *product* name, so two identical tablets were still indistinguishable.
## Changes
- **core**: `punktfunk_connect_ex10` = `connect_ex9` + `device_name` (**C ABI 20 → 21**, no wire change — `ex9` keeps its signature and passes a null name, which selects the old default). Names are truncated to `HELLO_NAME_MAX` on a character boundary (slicing a multi-byte name mid-scalar panics); covered by a unit test. `device_name()` now falls back to `gethostname()` (`.local` stripped, `localhost` rejected) before the placeholder.
- **apple**: new `DeviceName.current` (`Host.localizedName` on macOS, `UIDevice.current.name` on iOS/tvOS, hostname fallback for the 16+ model-only answer) is sent on every connect; the two pairing sheets and the ceremony fallback now read the same source instead of three separate literals.
- **android**: `Settings.Global.DEVICE_NAME` — the name the user typed in Settings — ahead of `Build.MODEL`. The "approve this device" prompt quotes the same string the connect actually knocks with.
- **web**: the approve dialog names the device and its fingerprint in its description. A pre-filled field is editable text, not a statement of *which* knock is being approved — the fingerprint is the only thing that stays unique when two devices share a name.
Existing paired devices keep their stored trust-store names; only new knocks change. The host and console binaries are behavior-unchanged apart from the dialog copy — clients must be rebuilt to send their real names.
## Verified
- `cargo fmt --check`, `cargo clippy -D warnings`, 392/392 punktfunk-core tests
- Apple: fresh 5-slice xcframework; `swift build` reaches "Build complete!" on macOS, iOS and tvOS
- Android: `:app:compileDebugKotlin` BUILD SUCCESSFUL (3 ABIs clean at the API-28 floor)
- web: codegen + build + `tsc --noEmit` clean
Not verified on-glass — needs a rebuilt client knocking at a host.
The web console shows exactly what the host was told, and the host was told
"This device" by every Apple client — so the outstanding-pairings view and the
approve dialog listed identical rows for an iPad, an Apple TV and a Mac.
The name rides `Hello::name`, which embedders fill from `client::device_name()`.
That resolves `COMPUTERNAME` (Windows-only) then `HOSTNAME` (a shell variable
never exported into a launchd-started process), and its last resort was the
literal "This device". No Apple GUI app has either variable, and the C ABI had
no device-name parameter for one to pass a better answer through, so every
Apple device fell through to the placeholder. Linux (/etc/hostname) and Windows
were unaffected; Android sent `Build.MODEL`, which names the product rather than
the unit — two of the same tablet were still indistinguishable.
- core: `punktfunk_connect_ex10` = `ex9` + `device_name` (C ABI v21, no wire
change — `ex9` keeps its signature and passes a null name for the old
default). Truncated to `HELLO_NAME_MAX` on a character boundary, since
slicing a multi-byte name mid-scalar panics.
- core: `device_name()` falls back to `gethostname()` before the placeholder,
so an embedder that passes nothing still gets a real name.
- apple: `DeviceName.current` (`Host.localizedName` / `UIDevice.current.name`,
falling back to the hostname when 16+ answers with the bare model) is sent on
connect, and the two pairing sheets plus the ceremony now read that one source
instead of three separate literals.
- android: `Settings.Global.DEVICE_NAME` — the name the user typed in Settings —
ahead of `Build.MODEL`. The "approve this device" prompt quotes the same
string the connect knocks with, so it can't send the user looking for a row
the console does not show.
- web: the approve dialog names the device and its fingerprint. A pre-filled
field is editable text, not a statement of which knock is being approved.
enricobuehler
scheduled this pull request to auto merge when all checks succeed 2026-08-15 08:42:51 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
The web console's outstanding-pairings view and its approve modal showed "This device" for every knocking client. The console is faithful — it renders
PendingDevice.nameexactly as the host received it, and the host really was told "This device".Root cause
The name rides
Hello::name, which embedders fill fromclient::device_name(). That resolvesCOMPUTERNAME(Windows-only) thenHOSTNAME(a shell variable, never exported into a launchd-started GUI process) and last-resorts to the literal"This device". No Apple GUI app has either variable, and the C ABI's connect had no device-name parameter for an embedder to pass a better answer through — so every Mac, iPad, iPhone and Apple TV knocked as the placeholder, and a console with three of them pending showed three identical rows. The Apple client already knew a good name (UIDevice.current.name/Host.localizedName, used in the PIN sheet); it had no way to send it on the connect path that produces a pending knock.Linux (
/etc/hostname) and Windows were unaffected. Android passedBuild.MODELon its own JNI path — no placeholder, but the product name, so two identical tablets were still indistinguishable.Changes
punktfunk_connect_ex10=connect_ex9+device_name(C ABI 20 → 21, no wire change —ex9keeps its signature and passes a null name, which selects the old default). Names are truncated toHELLO_NAME_MAXon a character boundary (slicing a multi-byte name mid-scalar panics); covered by a unit test.device_name()now falls back togethostname()(.localstripped,localhostrejected) before the placeholder.DeviceName.current(Host.localizedNameon macOS,UIDevice.current.nameon iOS/tvOS, hostname fallback for the 16+ model-only answer) is sent on every connect; the two pairing sheets and the ceremony fallback now read the same source instead of three separate literals.Settings.Global.DEVICE_NAME— the name the user typed in Settings — ahead ofBuild.MODEL. The "approve this device" prompt quotes the same string the connect actually knocks with.Existing paired devices keep their stored trust-store names; only new knocks change. The host and console binaries are behavior-unchanged apart from the dialog copy — clients must be rebuilt to send their real names.
Verified
cargo fmt --check,cargo clippy -D warnings, 392/392 punktfunk-core testsswift buildreaches "Build complete!" on macOS, iOS and tvOS:app:compileDebugKotlinBUILD SUCCESSFUL (3 ABIs clean at the API-28 floor)tsc --noEmitcleanNot verified on-glass — needs a rebuilt client knocking at a host.