Skip to content

Conversation

@rebelice
Copy link
Collaborator

@rebelice rebelice commented Dec 19, 2025

  • Replace zalando/go-keyring with 99designs/keyring for better backend support
  • Add platform-specific backend priority:
    • macOS: Keychain → encrypted file
    • Linux: SecretService → KWallet(for KDE) → encrypted file
    • Windows: WinCred → encrypted file
  • Add password dialog for missing passwords (fixes Stored connection won't connect #1)
  • Derive encrypted file password from machine ID + username
  • Improve error handling: show password prompt on read failure

Closes #1

Summary by CodeRabbit

  • New Features

    • Password dialog prompts users to enter missing credentials when connecting to databases.
    • Encrypted file-based fallback for password storage when the system keyring is unavailable.
    • Passwords entered are saved and reused for subsequent connections.
  • Bug Fixes / Reliability

    • Password storage errors are logged as warnings and do not block connection attempts.
  • Enhancements

    • Updated syntax highlighting and related tooling.

✏️ Tip: You can customize this high-level summary in your review settings.

- Replace zalando/go-keyring with 99designs/keyring for better backend support
- Add platform-specific backend priority:
  - macOS: Keychain → encrypted file
  - Linux: SecretService → KWallet → encrypted file
  - Windows: WinCred → encrypted file
- Add password dialog for missing passwords (fixes #1)
- Derive encrypted file password from machine ID + username
- Improve error handling: show password prompt on read failure

Closes #1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds a password dialog UI and integrates a new multi-backend keyring-based password store with file-backed encrypted fallback and machine-ID–derived file passwords; connection flows now prompt for and persist missing passwords and surface structured password storage/read errors.

Changes

Cohort / File(s) Change Summary
Dependency Updates
go.mod
Swapped github.com/zalando/go-keyring for github.com/99designs/keyring, added github.com/99designs/keyring, github.com/alecthomas/chroma/v2 and several indirect keyring/crypto/term deps; updated indirect dependency versions.
Password Error Types
internal/connection_history/errors.go
New sentinel ErrPasswordNotFound and wrapper error types PasswordSaveError and PasswordReadError with Error() and Unwrap() for structured error propagation.
Machine ID & Derived Password
internal/connection_history/machine_id.go
New cross-platform machine ID retrieval and deriveFilePassword (SHA‑256 + base64) with OS-specific implementations and hostname fallback.
Password Store (keyring + fallback)
internal/connection_history/password_store.go
New PasswordStore using keyring.Keyring with platform backends and file-based fallback encrypted by derived file password; updated NewPasswordStore, Save, Get, Delete, and IsUsingFallback() behavior and error mapping.
Connection History Manager
internal/connection_history/manager.go
Added ConnectionConfigResult and AddResult; Manager gains configDir; Add() now returns (*AddResult, error) to surface password save issues; introduced GetConnectionConfigWithPassword() returning password-missing state and SavePassword(); added IsUsingFallbackStorage().
UI: Password Dialog Component
internal/ui/components/password_dialog.go
New PasswordDialog component with PasswordSubmitMsg / PasswordCancelMsg, keyboard (Enter/Esc) and mouse handling, connection-info display, and APIs to get/set password and connection info.
App Integration: Password Flow
internal/app/app.go
App state extended with showPasswordDialog, passwordDialog, pendingConnectionInfo; Update loop and View render path handle password dialog messages; connection flows detect PasswordMissing, show dialog, save supplied password, and retry connections.
sequenceDiagram
    participant User
    participant App
    participant Manager as ConnManager
    participant Store as PasswordStore
    participant Dialog as PasswordDialog
    participant DB as Database

    User->>App: Select stored connection
    App->>Manager: GetConnectionConfigWithPassword(entry)
    alt Password present
        Manager->>Store: Get(key)
        Store-->>Manager: password
        Manager-->>App: ConnectionConfigResult{Config with Password}
        App->>DB: Connect
        DB-->>App: Success/Failure
    else Password missing / not found
        Manager-->>App: ConnectionConfigResult{PasswordMissing: true}
        App->>Dialog: SetConnectionInfo + Show
        Dialog->>User: Render prompt
        User->>Dialog: Submit(password)
        Dialog-->>App: PasswordSubmitMsg{password}
        App->>Manager: SavePassword(...)
        Manager->>Store: Set(key, password)
        Store-->>Manager: nil / PasswordSaveError
        App->>Manager: GetConnectionConfigWithPassword(entry)
        Manager->>Store: Get(key)
        Store-->>Manager: password
        Manager-->>App: ConnectionConfigResult{Config with Password}
        App->>DB: Connect
        DB-->>App: Success/Failure
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus review on:

  • internal/connection_history/password_store.go — backend selection, file-backend encryption key derivation, error mapping to new error types, and item key formatting (host:port:database:user).
  • internal/connection_history/machine_id.go — platform command usage, parsing robustness, fallbacks and error messages.
  • internal/connection_history/manager.go — changed public signatures (Add, GetConnectionConfigWithPassword) and new return types; verify all call sites.
  • internal/app/app.go — UI state transitions, pendingConnectionInfo handling, and retry logic after password submission.
  • internal/ui/components/password_dialog.go — keyboard/mouse handling correctness and focus/reset behavior.

🐰
I hopped to find the missing key,
A dialog asked politely of me,
Machine IDs and secret lore,
Saved in a file when rings say no more,
Now stored and found—hop, hop, hooray! 🎩✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: switching from zalando/go-keyring to 99designs/keyring and adding fallback password storage support.
Linked Issues check ✅ Passed The PR addresses issue #1 by implementing password dialog for missing passwords, using 99designs/keyring for better backend support, and deriving encrypted-file passwords from machine ID.
Out of Scope Changes check ✅ Passed All changes are directly related to the PR objectives: keyring migration, fallback storage mechanism, password dialog UI, and machine ID derivation for file encryption.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/improve-password-storage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (5)
internal/connection_history/machine_id.go (1)

94-110: Note: wmic is deprecated on modern Windows.

The wmic command is deprecated starting with Windows 10 21H1. While it still works on most systems, consider using PowerShell as a more future-proof alternative:

(Get-CimInstance -ClassName Win32_ComputerSystemProduct).UUID

This is low priority since the fallback to hostname ensures functionality.

internal/app/app.go (1)

2858-2870: Consider extracting duplicated Add result handling.

The same pattern for handling connectionHistory.Add results appears in three places (lines 2858-2870, 3029-3040, 3124-3135). Consider extracting to a helper method:

func (a *App) handleConnectionHistoryAdd(config models.ConnectionConfig) {
    result, err := a.connectionHistory.Add(config)
    if err != nil {
        log.Printf("Warning: Failed to save connection to history: %v", err)
    } else if result != nil && result.PasswordSaveError != nil {
        log.Printf("Warning: Failed to save password: %v", result.PasswordSaveError)
    }
    // Reload history in dialog
    history := a.connectionHistory.GetRecent(10)
    a.connectionDialog.SetHistoryEntries(history)
}
internal/ui/components/password_dialog.go (1)

43-62: Hardcoded colors may conflict with the theme system.

The input styles use hardcoded color values (e.g., #cba6f7, #cdd6f4) while a Theme is passed to the dialog. Consider using th colors for consistency.

That said, the CharLimit of 256 adequately addresses the 64-character password issue from #1.

🔎 Suggested improvement
 func NewPasswordDialog(th theme.Theme) *PasswordDialog {
 	input := textinput.New()
 	input.Placeholder = "Enter password"
 	input.EchoMode = textinput.EchoPassword
 	input.EchoCharacter = '•'
-	input.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#cba6f7"))
-	input.TextStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#cdd6f4"))
-	input.Cursor.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("#f38ba8"))
+	input.PromptStyle = lipgloss.NewStyle().Foreground(th.Info)
+	input.TextStyle = lipgloss.NewStyle().Foreground(th.Foreground)
+	input.Cursor.Style = lipgloss.NewStyle().Foreground(th.Error)
 	input.CharLimit = 256
 	input.Width = 40
 	input.Focus()
internal/connection_history/manager.go (2)

64-71: Consider the semantics when passwordStore is nil.

Returning false when passwordStore == nil implies "not using fallback" but actually means "no password storage at all." This could be confusing for callers who might interpret false as "using native keyring."

Consider adding a separate method like HasPasswordStorage() bool or documenting this edge case clearly.


207-210: Password deletion error is silently ignored.

While it's reasonable to not fail the delete operation if password cleanup fails, consider logging the error for debugging purposes.

🔎 Suggested improvement
 		if m.passwordStore != nil {
-			_ = m.passwordStore.Delete(entry.Host, entry.Port, entry.Database, entry.User)
+			if err := m.passwordStore.Delete(entry.Host, entry.Port, entry.Database, entry.User); err != nil {
+				log.Printf("Warning: Failed to delete password from store: %v", err)
+			}
 		}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c8144d2 and ae02dd0.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (7)
  • go.mod (3 hunks)
  • internal/app/app.go (11 hunks)
  • internal/connection_history/errors.go (1 hunks)
  • internal/connection_history/machine_id.go (1 hunks)
  • internal/connection_history/manager.go (8 hunks)
  • internal/connection_history/password_store.go (2 hunks)
  • internal/ui/components/password_dialog.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/connection_history/password_store.go (1)
internal/connection_history/errors.go (3)
  • PasswordSaveError (9-12)
  • ErrPasswordNotFound (6-6)
  • PasswordReadError (26-28)
🪛 OSV Scanner (2.3.0)
go.mod

[HIGH] 30-30: github.com/dvsekhvalnov/jose2go 1.5.0: Denial of service when decrypting attacker controlled input in github.com/dvsekhvalnov/jose2go

(GO-2023-2409)


[HIGH] 30-30: github.com/dvsekhvalnov/jose2go 1.5.0: Denial-of-Service (DoS) via crafted JSON Web Encryption (JWE) token high compression ratio in github.com/dvsekhvalnov/jose2go

(GO-2025-4123)


[HIGH] 30-30: github.com/dvsekhvalnov/jose2go 1.5.0: jose2go vulnerable to denial of service via large p2c value

(GHSA-6294-6rgp-fr7r)


[HIGH] 30-30: github.com/dvsekhvalnov/jose2go 1.5.0: jose2go is vulnerable to a JWT bomb attack through its decode function

(GHSA-9mj6-hxhv-w67j)


[HIGH] 30-30: github.com/dvsekhvalnov/jose2go 1.5.0: Denial of service when decrypting attack controlled input in github.com/dvsekhvalnov/jose2go

(GHSA-mhpq-9638-x6pw)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build (windows-latest)
🔇 Additional comments (17)
internal/connection_history/errors.go (1)

1-36: LGTM!

Well-structured error types following Go conventions with proper Unwrap() support for error chaining.

internal/app/app.go (3)

597-615: LGTM! Password submission flow is well-implemented.

The flow correctly:

  1. Creates connection config with the submitted password
  2. Attempts to save the password for future use (with error logging but non-blocking)
  3. Clears pending state before attempting connection
  4. Delegates to performConnection for the actual connection

2793-2815: LGTM! History entry connection with password fallback.

The implementation properly handles the case where a stored password is missing:

  • Creates a copy of the entry to prevent mutation issues
  • Initializes the password dialog with connection details
  • Transitions cleanly between dialogs

3406-3431: LGTM!

The renderPasswordDialog follows the same centering pattern as other dialogs, maintaining UI consistency.

internal/connection_history/password_store.go (1)

72-89: Fallback detection cannot accurately determine which backend is actually in use at runtime.

The keyring.Open() function attempts backends from the allowed list sequentially, continuing to the next backend on failure until one succeeds. However, the isUsingFallback() function relies on keyring.AvailableBackends(), which simply checks which backends are registered in the supportedBackends map—a compile-time and early initialization check, not a runtime availability check.

A backend can appear "available" during program startup but fail later when actually initialized. For example, SecretServiceBackend is only registered if dbus.SessionBus() succeeds during init, but the actual service connection (via libsecret.NewService()) can fail at runtime if the D-Bus service isn't running. Real-world issues confirm this: users report SecretService appearing in "Considering backends" but failing with "The name org.freedesktop.secrets was not provided" at runtime, forcing a fallback to FileBackend.

The 99designs/keyring library does not expose a public API to detect which backend was actually opened. The Keyring interface returned by Open() provides no method to determine the backend type, making it impossible for isUsingFallback() to reliably answer the question after the fact.

internal/ui/components/password_dialog.go (7)

1-12: LGTM!

Imports are appropriate for the Bubble Tea TUI framework components being used.


14-26: LGTM!

Zone IDs and message types follow standard Bubble Tea patterns for event handling.


28-41: LGTM!

Good design choice keeping the password only in the text input model rather than duplicating it in a separate field.


64-74: LGTM!

Appropriately clears the password input and refocuses when displaying a new connection prompt. The description provides clear context for which connection needs the password.


81-101: Consider whether empty password submission should be allowed.

Pressing Enter submits the password even if the input is empty. If empty passwords are not valid for connections, you might want to add validation here. If empty passwords are allowed (e.g., for local development DBs), this is fine as-is.


103-166: LGTM!

Clean view implementation with appropriate defensive checks and consistent styling. The zone marking for buttons enables proper mouse interaction.


168-197: LGTM!

Mouse handling correctly checks button type and action before processing. Getter methods are straightforward.

internal/connection_history/manager.go (5)

3-15: LGTM!

Added imports are appropriate for the new error handling and logging functionality.


17-30: LGTM!

The ConnectionConfigResult type cleanly encapsulates the password retrieval outcome, allowing callers to distinguish between missing passwords and retrieval errors.


42-52: Password store initialization failure is only logged, not exposed to caller.

If the password store fails to initialize (err != nil), the manager continues without password storage capability. The caller has no way to know this happened except by checking IsUsingFallbackStorage() (which returns false when passwordStore is nil).

This is acceptable for graceful degradation, but consider whether the caller should be informed so it can warn the user that passwords won't be persisted.


107-164: LGTM!

Good design separating the password save error from the connection history save error. This allows the connection to be saved even if password storage fails, with the caller able to inform the user appropriately.


218-264: LGTM!

Comprehensive handling of password retrieval scenarios. The use of errors.Is for checking ErrPasswordNotFound is correct for sentinel error comparison. The empty password check at lines 247-252 aligns well with the password dialog flow.

- Add UID fallback for username when env vars are empty (containers)
- Use errors.Is() instead of == for error comparison

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
internal/connection_history/password_store.go (2)

123-133: LGTM! Previous review concern addressed.

The error comparison now correctly uses errors.Is(err, keyring.ErrKeyNotFound) at line 127, addressing the previous review comment about proper error chain handling.


136-143: LGTM! Previous review concern addressed.

The error comparison now correctly uses !errors.Is(err, keyring.ErrKeyNotFound) at line 139, addressing the previous review comment about consistent error handling.

🧹 Nitpick comments (3)
internal/connection_history/machine_id.go (3)

18-39: Consider simplifying the function signature.

The function always returns nil for the error, making the error return value unnecessary. If fallbacks are guaranteed to succeed, consider removing the error return to simplify the signature to func deriveFilePassword() string.

Alternatively, if there are scenarios where password derivation could fail (e.g., insufficient entropy), return an actual error in those cases.


76-80: Add timeout to external command execution.

The exec.Command call lacks a timeout, which could cause the application to hang indefinitely if ioreg fails to respond. Use exec.CommandContext with a timeout to ensure bounded execution time.

🔎 Proposed fix with timeout
+	"context"
+	"time"
+
 func getDarwinMachineID() (string, error) {
-	cmd := exec.Command("ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "ioreg", "-rd1", "-c", "IOPlatformExpertDevice")
 	output, err := cmd.Output()
 	if err != nil {
 		return os.Hostname()
 	}

100-104: Add timeout to external command execution.

Similar to getDarwinMachineID, this command lacks a timeout. Use exec.CommandContext with a timeout to prevent indefinite hangs.

🔎 Proposed fix with timeout
 func getWindowsMachineID() (string, error) {
-	cmd := exec.Command("wmic", "csproduct", "get", "UUID")
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "wmic", "csproduct", "get", "UUID")
 	output, err := cmd.Output()
 	if err != nil {
 		return os.Hostname()
 	}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae02dd0 and af4d6ea.

📒 Files selected for processing (2)
  • internal/connection_history/machine_id.go (1 hunks)
  • internal/connection_history/password_store.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/connection_history/password_store.go (1)
internal/connection_history/errors.go (3)
  • PasswordSaveError (9-12)
  • ErrPasswordNotFound (6-6)
  • PasswordReadError (26-28)
🔇 Additional comments (6)
internal/connection_history/machine_id.go (2)

41-54: LGTM!

The platform-specific routing and fallback to hostname for unknown OSes is appropriate.


56-72: LGTM!

The implementation correctly checks standard Linux machine-id locations with appropriate fallback.

internal/connection_history/password_store.go (4)

1-18: LGTM!

The imports are appropriate and the PasswordStore struct is well-designed with fields for the keyring and fallback detection.


21-45: LGTM with dependency note.

The constructor properly initializes the keyring with platform-appropriate backends and file fallback. The error handling is appropriate.

Note: The FilePasswordFunc at line 31 depends on deriveFilePassword() from machine_id.go, which has a Windows compatibility issue flagged separately.


48-71: LGTM!

The platform-specific backend priorities correctly align with the PR objectives:

  • macOS: Keychain with file fallback
  • Linux: SecretService/KWallet with file fallback
  • Windows: WinCred with file fallback

100-120: LGTM!

The Save method correctly uses keyring.Item with appropriate metadata and returns PasswordSaveError on failure.

Comment on lines +29 to +32
if username == "" {
// Fallback for containers/service accounts without USER env
username = fmt.Sprintf("uid-%d", os.Getuid())
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

os.Getuid() fallback is Unix-specific and returns -1 on Windows.

While lines 29-32 add a fallback for containers/service accounts, os.Getuid() always returns -1 on Windows, resulting in the same uid--1 for all Windows users without environment variables. This reduces entropy and fails to provide machine-specific uniqueness.

Consider using a cross-platform approach, such as:

  • On Windows, query the Windows registry or use whoami command
  • Generate a random UUID and persist it in the config directory for consistency across restarts
  • Use os.UserHomeDir() or os.UserCacheDir() as a proxy for user identity
🔎 Proposed cross-platform fix
 	username := os.Getenv("USER")
 	if username == "" {
 		username = os.Getenv("USERNAME") // Windows
 	}
 	if username == "" {
-		// Fallback for containers/service accounts without USER env
-		username = fmt.Sprintf("uid-%d", os.Getuid())
+		// Cross-platform fallback: use home directory as user identifier
+		if homeDir, err := os.UserHomeDir(); err == nil {
+			username = homeDir
+		} else {
+			username = "default-user"
+		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if username == "" {
// Fallback for containers/service accounts without USER env
username = fmt.Sprintf("uid-%d", os.Getuid())
}
username := os.Getenv("USER")
if username == "" {
username = os.Getenv("USERNAME") // Windows
}
if username == "" {
// Cross-platform fallback: use home directory as user identifier
if homeDir, err := os.UserHomeDir(); err == nil {
username = homeDir
} else {
username = "default-user"
}
}
🤖 Prompt for AI Agents
In internal/connection_history/machine_id.go around lines 29 to 32, the current
fallback uses os.Getuid(), which is Unix-only and returns -1 on Windows; change
to a cross-platform fallback: first try os/user.Current() (or
os.UserHomeDir()/os.UserCacheDir() as a proxy) to obtain a stable identifier; if
that fails or yields empty on Windows, generate a random UUID, persist it to a
file in the user's config/cache directory (so it survives restarts), and use
that persisted UUID thereafter; ensure all errors are handled and that the
chosen identifier is normalized (no newlines) before returning.

Comment on lines +74 to +90
func isUsingFallback(requestedBackends []keyring.BackendType, ring keyring.Keyring) bool {
// If file backend is the only option, we're using fallback
if len(requestedBackends) == 1 && requestedBackends[0] == keyring.FileBackend {
return true
}

// Try to detect by checking available backends
availableBackends := keyring.AvailableBackends()
for _, b := range availableBackends {
if b != keyring.FileBackend {
// A native backend is available, likely not using fallback
return false
}
}

return true
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "password_store.go" -o -name "*password*store*" | head -20

Repository: rebelice/lazypg

Length of output: 106


🏁 Script executed:

cat -n internal/connection_history/password_store.go | head -100

Repository: rebelice/lazypg

Length of output: 3408


Fallback detection logic makes assumptions about backend selection rather than detecting actual usage.

The function checks keyring.AvailableBackends() which provides system-available backends, but this doesn't reflect which backend was actually selected by keyring.Open(). The 99designs/keyring library doesn't expose a public API to query the active backend—the Keyring interface only provides Get, Set, Remove, and Keys methods.

If a native backend (like SecretService on Linux) is available but fails during initialization, the keyring library would fall back to the file backend, yet this function would incorrectly return false based on availability alone.

For accurate fallback detection, consider:

  • Wrapping keyring.Open() to capture and store which backend succeeded during initialization
  • Performing a test operation to detect file backend behavior (e.g., checking for encrypted keyring file)
  • Accepting this as a known limitation and documenting it clearly if the current behavior is acceptable for your use case

@rebelice rebelice merged commit 5056f2e into main Dec 19, 2025
7 checks passed
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.

Stored connection won't connect

1 participant