aboutsummaryrefslogtreecommitdiffstats
path: root/docs/superpowers
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-12 10:05:56 +0200
committerDanilo M. <danix@danix.xyz>2026-09-12 10:05:56 +0200
commit15367430ddafd45c59310364234fa3ceefee3844 (patch)
tree558954121cf13ce1dc7b093f977b243d5e45311e /docs/superpowers
parent9cf3aaea8cc356a87321f3a8df96fdf68ff921b0 (diff)
downloadquickshell-15367430ddafd45c59310364234fa3ceefee3844.tar.gz
quickshell-15367430ddafd45c59310364234fa3ceefee3844.zip
docs: correct the notmuch malformed-query claim
The previous commit, the spec and the plan all said notmuch exits 0 on a malformed query while printing something that is not a count, and that validating the output as an integer therefore catches it. Measured properly, that is wrong in a way worth recording, because the truth is worse. notmuch fails two different ways. A rejected query prints nothing and exits 1: `notmuch count 'tag:unread and ('`. A query Xapian merely misparses returns a plausible wrong number and exits 0: `notmuch count 'tag:unread and (('` gives 41, and `'tag:unread and tag:'` gives 3. The second is undetectable by any check on the output, which is why the original claim was not just imprecise but inverted: the case it described as caught is the case nothing can catch. The integer validation still earns its place, on the first failure mode, where empty output would otherwise render as an empty inbox. The real defence against the second is that QUERY is a fixed string and is never built from anything, which the comment now says. The earlier measurement that produced the wrong claim read 40 as mangled output when it was a successful parse answering a different question. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WWL8JYHu7yhAdtx5pU9PMU
Diffstat (limited to 'docs/superpowers')
-rw-r--r--docs/superpowers/plans/2026-09-12-mail-overview.md23
-rw-r--r--docs/superpowers/specs/2026-09-12-mail-overview-design.md11
2 files changed, 23 insertions, 11 deletions
diff --git a/docs/superpowers/plans/2026-09-12-mail-overview.md b/docs/superpowers/plans/2026-09-12-mail-overview.md
index 63da50b..b1009ff 100644
--- a/docs/superpowers/plans/2026-09-12-mail-overview.md
+++ b/docs/superpowers/plans/2026-09-12-mail-overview.md
@@ -47,9 +47,14 @@ Do not re-derive these; they are why the code looks the way it does.
`position` key (top is the default) and `margin: 0`.
- There are **five** accounts, with notmuch tags `account-<key>` where `<key>`
matches an `[account.<key>]` section in `qtmaildir.conf`.
-- `notmuch` exits **0 even for a malformed query**: `notmuch count 'tag:unread
- and (('` printed `40` and exited 0. Exit status alone cannot detect failure,
- so every count is validated as a non-negative integer before use.
+- `notmuch` handles a malformed query in two ways, and only one is
+ detectable. `notmuch count 'tag:unread and (('` returns `41` and exits 0,
+ because Xapian accepts the fragment and answers a different question: a
+ wrong count, indistinguishable from a right one. `notmuch count 'tag:unread
+ and ('` prints nothing and exits 1. Validating the output as a non-negative
+ integer catches the second, which is the one that would otherwise render as
+ an empty inbox. The first is not catchable and not worth chasing: these
+ queries are fixed strings, not user input.
- An empty result is `0` from `count` and `[]` from `search --format=json`,
both with exit 0. That is a legitimate zero, not an error.
- `qtmaildir` accepts no command line arguments, so nothing can ask it to open
@@ -205,12 +210,14 @@ Ctrl-C the script.
- [ ] **Step 5: Verify the failure path renders as an error, not as zero**
-Point the script at a query that notmuch accepts and then mangles, by
-temporarily editing `QUERY` to `tag:unread and ((` and running it:
+Point the script at a query notmuch rejects outright, which is the failure the
+validation exists to catch: empty stdout and exit 1. Note that a *half* broken
+query like `tag:unread and ((` will NOT do, because Xapian accepts it and
+returns a plausible wrong number; use a single unclosed paren.
```bash
-sed -i "s/^QUERY=.*/QUERY='tag:unread and (('/" mail-overview/waybar-mail.sh
-./mail-overview/waybar-mail.sh | head -1
+sed -i "s/^QUERY=.*/QUERY='tag:unread and ('/" mail-overview/waybar-mail.sh
+timeout 5 ./mail-overview/waybar-mail.sh | head -1
```
Expected: `{"text":"!","tooltip":"notmuch count failed","class":"error"}`
@@ -220,7 +227,7 @@ query would silently read as a real count. Restore the query:
```bash
sed -i "s/^QUERY=.*/QUERY='tag:unread and tag:inbox'/" mail-overview/waybar-mail.sh
-./mail-overview/waybar-mail.sh | head -1
+timeout 5 ./mail-overview/waybar-mail.sh | head -1
```
Expected: the real count again.
diff --git a/docs/superpowers/specs/2026-09-12-mail-overview-design.md b/docs/superpowers/specs/2026-09-12-mail-overview-design.md
index a691d79..6b2423b 100644
--- a/docs/superpowers/specs/2026-09-12-mail-overview-design.md
+++ b/docs/superpowers/specs/2026-09-12-mail-overview-design.md
@@ -243,9 +243,14 @@ inotify watch on top of the timer.
## Error handling
-- notmuch exits 0 even for a malformed query, printing something that is not
- a count, so every count is validated as a non-negative integer and the exit
- status is not the test.
+- A malformed query is not reliably detectable. `notmuch count 'tag:unread
+ and (('` returns `41` and exits 0: Xapian accepts the fragment and answers a
+ different question, so a wrong count arrives looking exactly like a right
+ one. Nothing in this component can catch that, and it is not worth trying to;
+ the queries here are fixed strings, not user input.
+ What validation does catch is the clean failure: `notmuch count 'tag:unread
+ and ('` prints nothing and exits 1. Empty or non-numeric output therefore
+ renders as an error rather than as a zero.
- notmuch missing, the database locked, or a count that fails validation: the
count shows a dash, never a zero. A zero that is actually a failure reads as "no new mail", which is the
same class of mistake as reporting a libvirt host side figure as guest