Skip to content

fix: kill stale GPG agents before key generation#892

Open
nogeenharrie wants to merge 1 commit intomainfrom
fix/gpg-keygen-timeout
Open

fix: kill stale GPG agents before key generation#892
nogeenharrie wants to merge 1 commit intomainfrom
fix/gpg-keygen-timeout

Conversation

@nogeenharrie
Copy link
Copy Markdown
Contributor

@nogeenharrie nogeenharrie commented Apr 2, 2026

Summary

Fixes issue #815 - GPG key generation fails due to a timeout while waiting on a locked key database.

Problem

When another GPG process (from another application) holds a lock on the key database, key generation fails with:

gpg: Note: database_open 134217901 waiting for lock (held by 9485) ...
gpg: keydb_search failed: Connection timed out
gpg: key generation failed: SQL library used incorrectly

Solution

Before generating GPG keys, kill any stale gpg-agent processes that might be holding locks:

Executor::executeBlocking(gpgPath, {"--kill", "gpg-agent"});
Executor::executeBlocking(gpgPath, {"gpgconf", "--kill", "gpg-agent"});

This releases any stale locks on the GPG key database before attempting key generation.

Testing

  • Build passes with Qt 6.8.2 and Qt 5.15
  • All unit tests pass

Summary by CodeRabbit

  • Bug Fixes
    • Improved GPG key generation reliability by ensuring any stale GPG agent is terminated before generating new keys.
    • Reduces failures and hangs during key creation, leading to a smoother, more predictable key generation experience for users.

@coderabbitai

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/pass.cpp`:
- Around line 207-213: The two Executor::executeBlocking calls are invoking gpg
incorrectly and calling gpgconf as an argument; fix by deriving the gpgconf
executable path from gpgPath (use QFileInfo to get gpgPath's directory) then
call Executor::executeBlocking(gpgPath, {"--kill", "gpg-agent"}) should be
removed or replaced with the proper gpgconf invocation: build gpgconfPath from
QFileInfo(gpgPath).absolutePath() + QDir::separator() + "gpgconf" and call
Executor::executeBlocking(gpgconfPath, {"--kill", "gpg-agent"}); add `#include`
<QFileInfo> to the includes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 882a7afc-c438-4aab-be45-ca4c6ad895fa

📥 Commits

Reviewing files that changed from the base of the PR and between 7a9fe4c and 86cc302.

📒 Files selected for processing (1)
  • src/pass.cpp

Comment on lines +207 to +213
// Kill any stale GPG agents that might be holding locks on the key database
// This helps avoid "database locked" timeouts during key generation
QString gpgPath = QtPassSettings::getGpgExecutable();
if (!gpgPath.isEmpty()) {
Executor::executeBlocking(gpgPath, {"--kill", "gpg-agent"});
Executor::executeBlocking(gpgPath, {"gpgconf", "--kill", "gpg-agent"});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the correct command to kill gpg-agent using gpgconf?

💡 Result:

The correct command to kill gpg-agent using gpgconf is gpgconf --kill gpg-agent.

Citations:


🏁 Script executed:

# Check for existing includes in src/pass.cpp around the code area
head -50 src/pass.cpp | grep -E "^#include"

Repository: IJHack/QtPass

Length of output: 259


🏁 Script executed:

# Search for gpgconf references in the codebase
rg "gpgconf" --type cpp --type h

Repository: IJHack/QtPass

Length of output: 145


🏁 Script executed:

# Search for QFileInfo usage in the codebase
rg "QFileInfo" --type cpp --type h -A 2 | head -30

Repository: IJHack/QtPass

Length of output: 1429


🏁 Script executed:

# Search for QtPassSettings methods to see if there's a pattern for executable paths
rg "class QtPassSettings|getGpg|Executable" --type cpp --type h -A 1 | head -50

Repository: IJHack/QtPass

Length of output: 2967


Both agent-kill commands are incorrect and will fail silently.

Line 211 executes gpg --kill gpg-agent, which is invalid—GPG does not support the --kill option. Line 212 executes gpg gpgconf --kill gpg-agent (with "gpgconf" as an argument to the gpg binary) instead of running the gpgconf executable directly. The correct command is gpgconf --kill gpg-agent.

Derive the gpgconf path from gpgPath by extracting its directory. This pattern is already used elsewhere in the codebase (e.g., tst_util.cpp). Add #include <QFileInfo> to the includes and update the code:

Fix: correctly invoke gpgconf to kill the agent
   // Kill any stale GPG agents that might be holding locks on the key database
   // This helps avoid "database locked" timeouts during key generation
   QString gpgPath = QtPassSettings::getGpgExecutable();
   if (!gpgPath.isEmpty()) {
-    Executor::executeBlocking(gpgPath, {"--kill", "gpg-agent"});
-    Executor::executeBlocking(gpgPath, {"gpgconf", "--kill", "gpg-agent"});
+    // Derive gpgconf path from gpg path (they are typically in the same directory)
+    QFileInfo gpgInfo(gpgPath);
+    QString gpgconfPath = gpgInfo.absolutePath() + QDir::separator() + "gpgconf";
+#ifdef Q_OS_WIN
+    gpgconfPath += ".exe";
+#endif
+    if (QFile::exists(gpgconfPath)) {
+      Executor::executeBlocking(gpgconfPath, {"--kill", "gpg-agent"});
+    }
   }

Add to includes section:

`#include` <QFileInfo>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pass.cpp` around lines 207 - 213, The two Executor::executeBlocking calls
are invoking gpg incorrectly and calling gpgconf as an argument; fix by deriving
the gpgconf executable path from gpgPath (use QFileInfo to get gpgPath's
directory) then call Executor::executeBlocking(gpgPath, {"--kill", "gpg-agent"})
should be removed or replaced with the proper gpgconf invocation: build
gpgconfPath from QFileInfo(gpgPath).absolutePath() + QDir::separator() +
"gpgconf" and call Executor::executeBlocking(gpgconfPath, {"--kill",
"gpg-agent"}); add `#include` <QFileInfo> to the includes.

@coveralls

This comment has been minimized.

@codecov

This comment has been minimized.

Before generating GPG keys, kill any stale gpg-agent processes that
might be holding locks on the key database. This helps prevent
'timeout while waiting on locked key database' errors.

The issue occurs when another GPG process (possibly from another
application) holds a lock on the key database, causing key generation
to fail with timeout errors.
@nogeenharrie nogeenharrie force-pushed the fix/gpg-keygen-timeout branch from 86cc302 to b9d8446 Compare April 2, 2026 14:47
@nogeenhenk

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@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.

♻️ Duplicate comments (1)
src/pass.cpp (1)

209-212: ⚠️ Potential issue | 🔴 Critical

Invoke gpgconf directly; current command line is malformed.

Line 211 currently runs gpg with "gpgconf" as an argument, which is not the same as executing gpgconf --kill gpg-agent. This means stale-agent cleanup may not happen and the lock-timeout failure can persist.

Proposed fix
   QString gpgPath = QtPassSettings::getGpgExecutable();
   if (!gpgPath.isEmpty()) {
-    Executor::executeBlocking(gpgPath, {"gpgconf", "--kill", "gpg-agent"});
+    QFileInfo gpgInfo(gpgPath);
+    QString gpgconfPath = gpgInfo.absolutePath() + QDir::separator() + "gpgconf";
+#ifdef Q_OS_WIN
+    gpgconfPath += ".exe";
+#endif
+    const int rc = Executor::executeBlocking(gpgconfPath, {"--kill", "gpg-agent"});
+#ifdef QT_DEBUG
+    if (rc != 0) {
+      dbg() << "Failed to kill gpg-agent:" << gpgconfPath << rc;
+    }
+#endif
   }
What is the correct GnuPG command to terminate gpg-agent, and must it be executed via the gpgconf executable (not as an argument to gpg)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pass.cpp` around lines 209 - 212, The code is invoking the gpg binary
with "gpgconf" as an argument (gpgPath + {"gpgconf","--kill","gpg-agent"}) which
is wrong; instead execute the gpgconf program directly. Replace the call that
uses gpgPath and args {"gpgconf","--kill","gpg-agent"} so it invokes the gpgconf
executable (e.g. use a QtPassSettings::getGpgConfExecutable() if available or
the literal "gpgconf") and call Executor::executeBlocking(gpgconfPath,
{"--kill","gpg-agent"}) so gpg-agent is terminated properly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/pass.cpp`:
- Around line 209-212: The code is invoking the gpg binary with "gpgconf" as an
argument (gpgPath + {"gpgconf","--kill","gpg-agent"}) which is wrong; instead
execute the gpgconf program directly. Replace the call that uses gpgPath and
args {"gpgconf","--kill","gpg-agent"} so it invokes the gpgconf executable (e.g.
use a QtPassSettings::getGpgConfExecutable() if available or the literal
"gpgconf") and call Executor::executeBlocking(gpgconfPath,
{"--kill","gpg-agent"}) so gpg-agent is terminated properly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1cab2960-c85d-4025-bdf8-85c9af7c919a

📥 Commits

Reviewing files that changed from the base of the PR and between 86cc302 and b9d8446.

📒 Files selected for processing (1)
  • src/pass.cpp

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.

3 participants