with one click
qtpass-fixing
Bug fixing workflow for QtPass - find, fix, test, PR
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Bug fixing workflow for QtPass - find, fix, test, PR
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Documentation guide for QtPass - README, FAQ, localization
QtPass CI/CD workflow - run GitHub Actions locally with act, linters, formatters
Comprehensive guide for QtPass unit testing with Qt Test
QtPass localization workflow - translation files, updating, adding languages
QtPass localization audit - structural checks on .ts files (placeholders, HTML balance, mnemonics, mixed-script artifacts)
QtPass GitHub interaction - PRs, issues, branches, merging
| name | qtpass-fixing |
| description | Bug fixing workflow for QtPass - find, fix, test, PR |
| license | GPL-3.0-or-later |
| metadata | {"audience":"developers","workflow":"bugfix"} |
gh issue list --search "<keywords>"grep -r "pattern" src/qtpass-testing skill)tests/auto/<module>/# Build and run tests
make check
# Build binary
make -j4
# Create branch
git checkout -b fix/<issue-number>-short-description
# Commit with issue reference (always use -S for signing)
git commit -S -m "Fix <description> (#issue)"
# Push and create PR
git push origin fix/<branch>
gh pr create --title "Fix <description>" --body "## Summary\n- Fix description"
When CodeRabbit/CodeAnt-AI flag issues in PRs:
# After fixing, amend commit and force push
git add -A
git commit -S --amend --no-edit
git push --force
Qt classes like QGuiApplication::screenAt() can return null on some platforms.
Always verify pointers before dereferencing:
QScreen *screen = QGuiApplication::screenAt(pos);
if (!screen)
return;
Use QCoreApplication::arguments() instead of raw argv[] to let Qt normalize paths and handle quoting:
QStringList args = QCoreApplication::arguments();
Before using optional tools like pass-otp, check availability:
if (!passOTPexists) {
showError("pass-otp is required for OTP features");
return;
}
Use QDir for path normalization to handle / vs \ and ./.. components:
QDir dir;
QString normalized = dir.cleanPath(gpgIdPath);
For regular expression patterns matching URLs or special characters, use \\S+ for non-whitespace to avoid matching spaces:
QRegularExpression urlRegex("(\\S+)://(\\S+)");
For monospace fonts in tables/lists, use setStyleHint(QFont::Monospace) to avoid platform-specific defaults.
Avoid assertions that always evaluate to true:
// Bad - always true
QVERIFY(profiles.isEmpty() || !profiles.isEmpty());
QVERIFY(!store.isEmpty() || store.isEmpty());
// Good - meaningful check
QVERIFY(profiles.isEmpty());
QVERIFY2(store.isEmpty() || store.startsWith("/"), "Pass store should be empty or a plausible path");
Always verify that test setup operations succeed:
// Bad - ignores return value
(void)QDir(srcDir.path()).mkdir("source");
(void)f1.open(QFile::WriteOnly);
// Good - verify success
QVERIFY(QDir(srcDir.path()).mkdir("source"));
QVERIFY(f1.open(QFile::WriteOnly));
Use "cannot" instead of "can't" for formal consistency:
// Bad
// On Windows, we can't safely backup
// Good
// On Windows, we cannot safely backup
Use the actual year the file was created — never a placeholder, never a year range. Repository convention is a single literal year (the existing files are 2014/2015/2016/2018/2020/2026, all real years; PR #1162 review explicitly flagged YYYY placeholders).
// Bad — placeholder; reviewers will ask for a real year
// SPDX-FileCopyrightText: YYYY Your Name
// Bad — year range
// SPDX-FileCopyrightText: 2014-2026 Your Name
// Good — single real year
// SPDX-FileCopyrightText: 2026 Your Name
When showing an error dialog followed by reject() in a constructor, add return to prevent the constructor from continuing with invalid state:
// After error dialog in constructor
if (users.isEmpty()) {
QMessageBox::critical(parent, tr("Error"), tr("No users found"));
reject();
return; // Prevent constructor from continuing
}
When storing references to list items in UI (e.g., Qt::UserRole), prefer indices over pointers to avoid dangling references:
// Instead of storing pointer to local reference
for (const auto &user : m_userList) {
item->setData(Qt::UserRole, QVariant::fromValue(&user)); // DANGLING!
}
// Store index instead
for (int i = 0; i < m_userList.size(); ++i) {
item->setData(Qt::UserRole, QVariant::fromValue(i));
}
// Later, lookup by index
bool success = false;
const int index = item->data(Qt::UserRole).toInt(&success);
if (success && index >= 0 && index < m_userList.size()) {
m_userList[index].enabled = item->checkState() == Qt::Checked;
}
For list items or labels indicating status (e.g., secret keys), prefer QPalette colors over hardcoded values for better accessibility:
// Hardcoded may have poor contrast on some themes
item->setForeground(Qt::blue);
// Theme-aware alternative
const QPalette palette = QApplication::palette();
item->setForeground(palette.color(QPalette::Link));
When indicating status through colors (invalid, expired, partial), add text prefixes or tooltips for color blind users:
// Color only - not accessible
item->setBackground(Qt::darkRed);
// Color + text + tooltip - accessible
item->setBackground(Qt::darkRed);
item->setText(tr("[INVALID] ") + originalText);
item->setToolTip(tr("Invalid key"));
Add debug logging for validation failures using #ifdef QT_DEBUG:
bool success = false;
const int index = item->data(Qt::UserRole).toInt(&success);
if (!success) {
#ifdef QT_DEBUG
qWarning() << "UsersDialog::itemChange: invalid user index data for item";
#endif
return;
}
if (index < 0 || index >= m_userList.size()) {
#ifdef QT_DEBUG
qWarning() << "UsersDialog::itemChange: user index out of range:" << index;
#endif
return;
}
CI tests both Qt 5.15 and Qt 6.8. Some Qt 6 APIs do not exist in Qt 5.15:
| Qt 6 API | Qt 5.15-compatible alternative |
|---|---|
QStringList::removeIf() | erase/remove_if with a predicate lambda |
QList::removeIf() | same pattern |
// Bad — Qt 6.1+ only, fails on Qt 5.15
env.removeIf([&key](const QString &e) { return e.startsWith(key); });
// Good — works on Qt 5.15 and Qt 6.x
env.erase(std::remove_if(env.begin(), env.end(),
[&key](const QString &entry) {
return entry.startsWith(key);
}),
env.end());
When the goal is to drop env vars by name prefix, use std::remove_if with startsWith. Don't reach for QStringList::filter() — it does the opposite (keeps matching entries) and matches substrings anywhere in the string, so even as a selection it would over-match:
// Bad — filter() returns entries containing key, also matching "FOOBAR=value"
// when key is "FOO". And it selects, not removes; the assignment then drops
// every entry that didn't match — opposite of the intent.
env = env.filter(key);
// Good — explicit prefix-based removal
env.erase(std::remove_if(env.begin(), env.end(),
[&key](const QString &e) { return e.startsWith(key); }),
env.end());
When iterating over a Qt container that is a non-const member variable, add std::as_const() to prevent Qt from making an implicit copy (detach):
// Bad — may detach the container implicitly
for (const auto &field : m_fields) { ... }
// Good — no detach, no copy
for (const auto &field : std::as_const(m_fields)) { ... }
When comparing booleans, use && instead of == to avoid unexpected true values:
// Bad - yields true when both are false (e.g., public key case)
bool secret = false;
bool isSec = (type == GpgRecordType::Sec); // false
handlePubSecRecord(props, secret == isSec); // false == false = true!
// Good - requires both conditions to be true
handlePubSecRecord(props, secret && (type == GpgRecordType::Sec));
| File | Purpose |
|---|---|
| src/mainwindow.cpp | Main UI, tree view, dialogs |
| src/pass.cpp | GPG operations, path handling |
| src/util.cpp | Utilities, regular expression, file ops |
| src/filecontent.cpp | Password file parsing |
| src/imitatepass.cpp | CLI pass imitation |
| src/configdialog.cpp | Settings dialog |
| src/executor.cpp | Command execution |
See qtpass-linting skill for full CI workflow. Pattern:
# Run linter locally BEFORE pushing
act push -W .github/workflows/linter.yml -j build
npx prettier --write <config-file>
npx prettier --write .github/workflows/*.yml
npx prettier --write "*/*/SKILL.md"
# Check formatting
clang-format --style=file --dry-run <source-file>
# Apply formatting
clang-format --style=file -i <source-file>
UI bugs often involve signal/slot connections, widget state, or clipboard operations.
Common patterns:
Debugging:
// Add debug output
qDebug() << "Button clicked, state:" << ui->someWidget->isVisible();
GPG-related bugs often involve command execution, path handling, or key operations.
Common patterns:
Debugging:
// Enable verbose GPG output
// Check /tmp for temporary GPG files
Path handling bugs often involve separators, encoding, or model indexing.
Common patterns:
Debugging:
qDebug() << "Path:" << path << "Cleaned:" << QDir::cleanPath(path);
For proper code analysis (resolving Qt types like QString), generate compile_commands.json:
./scripts/generate-compile-commands.sh
This enables clangd/LSP to provide accurate completions and catch real issues. Without it, the LSP shows false positives about missing Qt headers.
When the LSP shows "(fix available)" on errors:
Ctrl+. (Quick Fix)Alt+Enter on the errortextDocument/codeAction via your LSP pluginNote: LSP suggestions are just that - suggestions. Always verify the fix makes sense before applying, especially for complex refactoring.
To check a file directly for issues:
# Generate compile_commands.json first (required for Qt headers)
./scripts/generate-compile-commands.sh
# Check a specific file
clangd --check=/path/to/file.cpp
Common clangd diagnostics:
[performance-unnecessary-copy-initialization] - Use const T& instead of const T[readability-static-definition] - Consider making static definitions inline