
Photo by Jakub Żerdzicki on Unsplash
Lookout is a menu-bar app with one job: tell me when something on GitHub needs me. Mentions, review requests, failing builds, issues on my own repositories. A pill in the menu bar goes red, I click it, I deal with whatever it is.
On Thursday someone opened three pull requests on QuitProtect. Lookout said nothing. It carried on saying nothing on Friday and Saturday. I found out on Sunday, by accident.
The temptation was to assume it had stopped polling, or that my access token had expired. Both would have been easy to believe and both were wrong. Lookout was running perfectly and had been all along.
The app asks GitHub four questions on every poll. One of them covers a specific gap: your notifications inbox only tells you about repositories you are watching, and owning a repository does not automatically mean you watch it. So something opened by a stranger on a repo you own can generate no notification whatsoever. I hit that in May, when an external bug report sat unseen for weeks, and added a search to catch it.
That search asked for is:issue.
In GitHub’s search language, is:issue excludes pull requests. They are a separate kind of thing. So a pull request opened by someone else on my own repository matched precisely nothing:
Four nets, one hole, shaped exactly like a pull request from a stranger.
I checked rather than reasoned about it, which is the only way to be sure with this sort of thing. Asking GitHub the old question returned zero results. Asking it the new one returned exactly three — the missing pull requests, by name.
What makes this worth writing down is not the bug, it is the shape of it. The blind spot was found and fixed once, in May, for issues. Nobody went back and asked which other kinds of thing the same net was silently excluding. When you add a safety net for one kind of item, that is exactly the moment to ask what else it drops on the floor.
Both are covered now, and pull requests appear in green.
While I was in there I found a slower version of the same failure.
The owned-repository search only looked at issues created in the last 365 days. That window had a real reason when I wrote it at the end of May. Minify, a PHP library I forked and froze in 2014, still had eighty-five open issues on its tracker at that point — the newest from 2015, the oldest from the Google Code era. A brand new poll source whose first act was to report eighty-five things would have been worse than useless.
Three weeks later I sat down and read all eighty-five, and answered every one by hand. That tracker has read 0 open ever since.
So the condition the filter was written for stopped existing in June. The filter carried on regardless — except that what it now dropped was not legacy noise, it was any issue still open on its first birthday, discarded silently on the day it turned one. The same failure as the pull requests, just on a twelve-month delay.
An open issue is a request for your attention and it does not expire. There is no age filter now. The escape hatch survives for anyone who inherits a tracker full of imported history — set Lookout.issueLookbackDays and only issues newer than that are considered — but you have to ask for it, and the default is to show you everything that is open.
The lesson is the more useful half. A filter written for a temporary condition outlives the condition, and nothing tells you when it has. Those eighty-five issues were the entire reason that window existed. I cleared them, wrote a rather pleased-with-myself post about clearing them, and it still never once occurred to me to ask what else in my own software had quietly been built around their existence.
Then there was the small one, which had been staring at me since the day the app shipped.
In the corner of the panel, a caption. It read in 0 sec. Always. Not sometimes, not occasionally — every time I opened it, since March.
The word in was the clue, and I missed it for months. That is countdown phrasing. When I wrote that line I meant it to count down to the next check of GitHub. But the value it was counting toward was the time of the last check, formatted at the exact instant that check had finished. The answer was therefore always zero. And nothing ever redrew it afterwards, so that zero simply sat there until the next poll produced an identical one.
Two faults compounding: a sentence pointed at the wrong date, and a clock that never moved.
It now does what it always intended to do — counts down to the next check, ticking every second, using the interval GitHub itself asks clients to respect rather than one I invented. The time of the last check moved to the tooltip, where it belongs.
There is a lesson in there about how long you can look at something without seeing it. That caption was in front of me perhaps a thousand times.
Unchanged. Same polling behaviour, same handling of sleep and wake, same single pill in the menu bar. The token requirements have not changed either — still a classic personal access token, for reasons I have complained about elsewhere.
Version 1.1.0 is available now, via the download page or Homebrew.
And the three pull requests that started this? All dealt with, three days late, in a single afternoon.