Skip to content

auth login/status/doctor hang forever (no timeout) when keyring's D-Bus handshake stalls; BASECAMP_NO_KEYRING=1 works but is undocumented #800

Description

@jethrojones

Summary

Any basecamp command that touches stored credentials (auth login, auth status, doctor, projects list, etc.) hangs forever, with no error and no timeout, on a Linux desktop where a D-Bus session bus and gnome-keyring-daemon are both present and working normally. Root cause: the github.com/zalando/go-keyring Secret Service (D-Bus) backend's low-level D-Bus auth handshake never completes, and nothing times it out.

Environment

  • basecamp version 0.11.0
  • Linux (Omarchy/Hyprland desktop), gnome-keyring-daemon running (pkcs11,secrets components), registered on the session bus as org.freedesktop.secrets / org.gnome.keyring
  • dbus-send --session ... org.freedesktop.DBus.ListNames works fine and returns immediately — the bus itself and other D-Bus clients are healthy
  • System clock is NTP-synced

Reproduction

$ basecamp auth status --json
# hangs indefinitely, no output, no timeout

Same hang on basecamp doctor --json, basecamp auth login (both default browser flow and --device-code/--remote), and any data command like basecamp projects list once credentials need to be read. --version and --help return instantly (they don't touch the credential store), which is what pointed us at credential storage rather than the network as the culprit — a plain curl -v https://3.basecamp.com / https://launchpad.37signals.com completes in well under 200ms the whole time this is hanging, ruling out a network/proxy issue.

Diagnosis

Sent SIGQUIT to the hung process to get a goroutine dump (Go's default behavior — dumps all stacks and exits). The main goroutine is parked here even during a device-code login after the OAuth exchange has already succeeded (Authorization.GetInfo and People.Me both completed):

goroutine 1 [chan receive]:
...
github.com/zalando/go-keyring.secretServiceProvider.Set(...)
	github.com/zalando/go-keyring@.../secret_service/...
...

with a companion goroutine permanently blocked reading from the D-Bus unix socket during the SASL/EXTERNAL auth handshake:

github.com/godbus/dbus/v5.(*Conn).inWorker(...)
	github.com/godbus/dbus/v5@v5.2.2/conn.go:390
net.(*UnixConn).ReadMsgUnix(...)
internal/poll.(*FD).ReadMsg(...)

So: the CLI opens its own new D-Bus connection (rather than reusing/sharing the session bus connection other tools use) to talk to the Secret Service, and on this machine that connection's auth handshake never resolves — even though the exact same bus is trivially reachable via dbus-send in another process at the same moment. Whatever the cause on the D-Bus/gnome-keyring side, the CLI has no timeout around this call, so it wedges the whole process (and, transitively, auth login, auth status, and doctor, since they all go through the same credential store).

Confirmed workaround

Setting BASECAMP_NO_KEYRING=1 (an env var already compiled into the binary, presumably intended for exactly this) skips the keyring path entirely and every command returns instantly:

$ BASECAMP_NO_KEYRING=1 basecamp auth status --json
{"ok":true,"data":{"authenticated":true,...}}   # instant

This isn't documented anywhere in --help output (auth --help, config --help, top-level --help), so it took a strings pass over the binary to discover. Also worth noting: basecamp already has a message for the explicit-error keyring-unavailable case ("system keyring unavailable (%v), credentials stored in plaintext at %s"), it just doesn't reach that fallback when the keyring call hangs instead of erroring.

Suggested fixes

  1. Wrap the keyring Set/Get calls in a bounded context/timeout (a few seconds is plenty) and fall back to the existing plaintext-storage path on timeout, the same as the existing "keyring unavailable" error path.
  2. Document BASECAMP_NO_KEYRING in --help output and/or basecamp doctor, so people hitting this don't have to reverse-engineer it.
  3. Possibly related to the gnome-keyring Secret Service/D-Bus issues already tracked in basecamp/hey-cli#352 and basecamp/hey-cli#453 (see also this comment diagnosing the OpenSession/GVariant assertion chain on GNOME Keyring 50.0 on Omarchy) — different symptom (daemon aborts vs. a client-side hang with no timeout), same subsystem (zalando/go-keyring + gnome-keyring D-Bus Secret Service on Omarchy/Hyprland), so may share a root cause worth checking together.

Happy to provide the full goroutine dump or test further if useful.

Activity

  1. robzolkos commented on Sep 30, 2026

    @robzolkos
    Collaborator

    Confirmed the indefinite-wait bug with an automated regression exercising the real credential-store → go-keyring → godbus path against a Unix socket that stalls D-Bus authentication. The test-only commit fails with the desktop read still blocked after 12 seconds; headless and explicit-bypass controls pass. With the fix, that desktop read returns after approximately 10 seconds.

    Two dependent PRs are open for review:

    1. fix(credstore): support bounded keyring operations cli#79 — opt-in deadlines for subsequent keyring reads/writes/deletes/migration, including queue waits.
    2. fix(auth): prevent stalled keyrings from hanging Linux commands #805 — Linux 10-second probe and operation limits, bounded doctor/legacy-migration paths, correct recovery hints, and discoverable BASECAMP_NO_KEYRING help.

    The initial probe retains warned file fallback. A timeout after an established keyring operation is an error, not a silent switch to plaintext or a missing-login diagnosis. Started writes/deletes can still complete after timeout, so that store refuses further operations. Interactive macOS/Windows behavior is unchanged.

    Both repositories' full local checks and all applicable hosted checks are green. Fresh Fizzy tests and command race tests pass against the shared library; HEY owns a separate store. No Basecamp API/SDK change is needed. Red-test-before-fix commits are preserved in both PRs.

    Neither PR is merged or has auto-merge enabled. The shared-library PR should land first after review. The specific GNOME Keyring crash relationship remains unverified; the stalled-handshake reproduction establishes the CLI's missing bounds.

    The existing immediate opt-out is BASECAMP_NO_KEYRING=1; it uses plaintext file storage rather than the system keyring.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions