mirror of
https://github.com/MadsLorentzen/ai-job-search.git
synced 2026-09-17 00:26:26 +00:00
fix(gmail-sync): replace in:inbox with -in:sent -in:drafts
in:inbox matches only messages currently in the Inbox, so it silently
excluded archived mail and everything routed past the inbox by a
label-and-archive filter - exactly the mail matched by the job-search
label Step 3.1 hunts for. The stated intent ("skip sent/drafts") is what
the negative operators express. Failure mode was silent under-detection
that read as "no updates" and left the tracker stale. Review finding F18
(2026-08-19).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
0e054f16e7
commit
4ed5fee221
@@ -46,9 +46,9 @@ Lookback window: `since <date>` argument if given, else `state.last_sync` if set
|
|||||||
- A quoted-name OR-group of the open applications' company names, e.g. `{"Acme Corp" "BigCo"}`
|
- A quoted-name OR-group of the open applications' company names, e.g. `{"Acme Corp" "BigCo"}`
|
||||||
- A sender-domain OR-group of common ATS platforms: `{from:greenhouse.io from:lever.co from:myworkday.com from:ashbyhq.com from:smartrecruiters.com from:icims.com from:bamboohr.com}`
|
- A sender-domain OR-group of common ATS platforms: `{from:greenhouse.io from:lever.co from:myworkday.com from:ashbyhq.com from:smartrecruiters.com from:icims.com from:bamboohr.com}`
|
||||||
- The lookback bound, e.g. `newer_than:30d` or `after:2026/06/15`
|
- The lookback bound, e.g. `newer_than:30d` or `after:2026/06/15`
|
||||||
- `in:inbox` (skip sent/drafts - status signals come from what employers send you, not what you sent them)
|
- `-in:sent -in:drafts` (status signals come from what employers send you, not what you sent them; the negative operators keep **archived** mail and label-filtered mail in scope - restricting to the Inbox instead would silently drop both, including exactly the mail matched by the job-search label from step 1, since the standard filter that applies such a label also archives it)
|
||||||
|
|
||||||
Example: `newer_than:30d in:inbox ({"Acme Corp" "BigCo"} OR {from:greenhouse.io from:lever.co from:myworkday.com from:ashbyhq.com})`
|
Example: `newer_than:30d -in:sent -in:drafts ({"Acme Corp" "BigCo"} OR {from:greenhouse.io from:lever.co from:myworkday.com from:ashbyhq.com})`
|
||||||
|
|
||||||
4. Call `search_threads` with `view: THREAD_VIEW_MINIMAL`, `pageSize: 50`, paginating via `pageToken` until exhausted or results are clearly outside the relevant window.
|
4. Call `search_threads` with `view: THREAD_VIEW_MINIMAL`, `pageSize: 50`, paginating via `pageToken` until exhausted or results are clearly outside the relevant window.
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,13 @@ per-file diff commands.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- **`/gmail-sync` no longer restricts its search to the Inbox** - the query used
|
||||||
|
`in:inbox` to "skip sent/drafts", but that operator also excludes every archived
|
||||||
|
message, and self-defeatingly the mail matched by the very job-search label Step 3.1
|
||||||
|
hunts for (the standard filter that applies such a label also archives). The query now
|
||||||
|
uses `-in:sent -in:drafts`, which matches the stated intent exactly. The failure mode
|
||||||
|
was silent under-detection: a missed rejection or interview invite read as "no
|
||||||
|
updates". Pinned by the new `tests/test_gmail_sync_command.py`.
|
||||||
- **`/upskill` no longer divides by a blank `fit_rating`** - `/outcome` creates tracker
|
- **`/upskill` no longer divides by a blank `fit_rating`** - `/outcome` creates tracker
|
||||||
rows for applications made outside the workflow with no fit evaluation, so their
|
rows for applications made outside the workflow with no fit evaluation, so their
|
||||||
`fit_rating` is blank, and Step 3.3's `(100 - fit_rating) / 100` had no rule for that.
|
`fit_rating` is blank, and Step 3.3's `(100 - fit_rating) / 100` had no rule for that.
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
"""Guards for /gmail-sync's Gmail query semantics.
|
||||||
|
|
||||||
|
The command's stated intent is "skip sent/drafts - status signals come
|
||||||
|
from what employers send you". `in:inbox` does not mean that: it matches
|
||||||
|
only messages currently IN the inbox, so it also excludes every archived
|
||||||
|
message - and, self-defeatingly, the mail matched by the very
|
||||||
|
job-search label Step 3.1 hunts for, because the standard filter that
|
||||||
|
applies such a label also archives ("skip the inbox"). The correct
|
||||||
|
operators for the stated intent are `-in:sent -in:drafts` (review
|
||||||
|
finding F18, 2026-08-19). The failure mode is silent under-detection: a
|
||||||
|
missed rejection or interview invite just looks like "no updates".
|
||||||
|
"""
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
REPO = Path(__file__).resolve().parent.parent
|
||||||
|
GMAIL_SYNC = REPO / ".claude" / "commands" / "gmail-sync.md"
|
||||||
|
|
||||||
|
|
||||||
|
class TestGmailQueryOperators(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.text = GMAIL_SYNC.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
def test_query_excludes_sent_and_drafts_explicitly(self):
|
||||||
|
self.assertIn(
|
||||||
|
"-in:sent -in:drafts",
|
||||||
|
self.text,
|
||||||
|
"the query must exclude sent/drafts with negative operators, "
|
||||||
|
"which keep archived and label-filtered mail in scope",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_query_never_restricts_to_the_inbox(self):
|
||||||
|
self.assertNotIn(
|
||||||
|
"in:inbox",
|
||||||
|
self.text.replace("-in:sent", "").replace("-in:drafts", ""),
|
||||||
|
"in:inbox silently drops archived mail and everything a "
|
||||||
|
"label-and-archive filter routed past the inbox - exactly the "
|
||||||
|
"mail the label search in Step 3.1 exists to find",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user