Skip to content

refactor(pass): pull store from cobra context#556

Merged
joe0BAB merged 1 commit into
mainfrom
fix/pass-2
Jun 11, 2026
Merged

refactor(pass): pull store from cobra context#556
joe0BAB merged 1 commit into
mainfrom
fix/pass-2

Conversation

@joe0BAB

@joe0BAB joe0BAB commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Subcommands previously took a fully-initialized store.Store as a constructor argument, which forced the caller to open the OS keychain before cobra had even dispatched the subcommand. That eager init is the root cause of the headless-hang reported in #405: on systems with no D-Bus session bus the keychain dial blocks indefinitely, taking down commands that never needed the keychain in the first place.

Drop the store argument from GetCommand/SetCommand/ListCommand/RmCommand and have each RunE pull the live store from the cobra command context via the new commands.StoreFrom helper. ErrNoStoreInContext is returned when no store has been attached, so a subcommand that runs without the root having opened the keychain fails with a clear error rather than dereferencing nil.

Root and the OTel wrapper live in the consuming binary (secrets-engine- private) — once it switches to a PersistentPreRunE that calls commands.WithStore lazily, the headless pass version / --help paths will no longer touch the keychain.

Tests now exercise each subcommand directly, attaching a mock store via WithStore and running the command in isolation. The telemetry test was tied to the now-removed withOTEL wrapper and has been dropped along with the otel/term direct dependencies; vendor/ is updated accordingly.

Subcommands previously took a fully-initialized store.Store as a
constructor argument, which forced the caller to open the OS keychain
before cobra had even dispatched the subcommand. That eager init is the
root cause of the headless-hang reported in #405: on systems with no
D-Bus session bus the keychain dial blocks indefinitely, taking down
commands that never needed the keychain in the first place.

Drop the store argument from GetCommand/SetCommand/ListCommand/RmCommand
and have each RunE pull the live store from the cobra command context
via the new commands.StoreFrom helper. ErrNoStoreInContext is returned
when no store has been attached, so a subcommand that runs without the
root having opened the keychain fails with a clear error rather than
dereferencing nil.

Root and the OTel wrapper live in the consuming binary (secrets-engine-
private) — once it switches to a PersistentPreRunE that calls
commands.WithStore lazily, the headless `pass version` / `--help` paths
will no longer touch the keychain.

Tests now exercise each subcommand directly, attaching a mock store via
WithStore and running the command in isolation. The telemetry test was
tied to the now-removed withOTEL wrapper and has been dropped along with
the otel/term direct dependencies; vendor/ is updated accordingly.
@joe0BAB

joe0BAB commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

Replaces #554

@joe0BAB joe0BAB marked this pull request as ready for review June 11, 2026 09:51

@docker-agent docker-agent Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assessment: 🟡 NEEDS ATTENTION

This PR correctly implements the lazy-store pattern — pulling store.Store from cobra's command context rather than passing it as a constructor argument. The refactor addresses the headless-hang root cause (#405) and all four subcommands (get, set, list, rm) properly propagate ErrNoStoreInContext. One minor defensive-coding note is included below.

}

func StoreFrom(ctx context.Context) (store.Store, error) {
s, ok := ctx.Value(storeCtxKey{}).(store.Store)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential: StoreFrom cannot detect typed-nil stores

The type assertion ctx.Value(storeCtxKey{}).(store.Store) only sets ok = false when the value is absent or its dynamic type does not implement store.Store. If a caller passes a typed-nil concrete pointer — e.g. var s *keychain.Store; commands.WithStore(ctx, s) — the assertion succeeds (ok == true) and StoreFrom returns a non-nil interface wrapping a nil pointer, with no error. Any subsequent method call on that store will panic.

This scenario requires the consuming binary (being wired up separately in secrets-engine-private) to call WithStore with a typed-nil value, which is an unusual mistake. Still worth guarding against, e.g.:

if s == nil {
    return nil, ErrNoStoreInContext
}

Note: verification of this finding was inconclusive — surfacing for author evaluation.

@joe0BAB joe0BAB merged commit c9332e7 into main Jun 11, 2026
23 checks passed
@joe0BAB joe0BAB deleted the fix/pass-2 branch June 11, 2026 10:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants