Which bugs affected paying customers: joining Linear and Stripe
It is Tuesday morning and the support lead has a list of eleven issues that shipped fixes last week. The CEO asked a question in Slack that sounds simple: of the bugs we fixed, which ones actually hit people who pay us, and how much revenue was sitting behind them.
The support lead has two screens open. On the left, Linear, filtered to Done in the last seven days, label bug, sorted by completion date. Each issue has a title, a state, an assignee, a cycle, sometimes a customer request linked from Intercom, and a description written by whoever filed it. On the right, Stripe, the Customers list, with a search box and a filter panel for subscription status, plan, MRR band, and created date.
Neither screen answers the question. The left one knows what broke. The right one knows who pays. Nothing on either screen tells you the overlap, and the reason is more interesting than a missing feature.
What Linear shows on its own
Linear is precise about work. For each of those eleven issues it will tell you the exact state history, who moved it, which cycle and project it belongs to, how long it sat in review, and which pull request closed it. If your team uses labels well you get severity, area, and platform. If you use the Intercom or Zendesk integration, issues carry linked customer requests, so an issue can show you that three conversations were attached to it and which organisations those conversations came from.
That last part is the closest Linear gets, and it is genuinely useful. But it stops in two specific places.
First, coverage. Customer requests exist on issues that came in through support. Plenty of bugs are found internally, by an engineer, by an on-call alert, by someone testing a release. Those issues have no linked requests at all, which does not mean no customer was affected. It means nobody wrote a ticket.
Second, and more important, Linear knows the customer as a name. "Northwind Logistics" is a string. Linear has no idea whether Northwind is on a $19 monthly plan, a $4,000 annual contract, in trial, or churned three weeks ago. Nothing in Linear's data model carries a subscription. It was never supposed to.
What Stripe shows on its own
Stripe is precise about money. It will tell you exactly which customers have active subscriptions, what each one pays, when they last had a failed payment, what their lifetime value is, and who has a cancellation scheduled at period end. You can segment by plan, by MRR, by billing interval, by country. Stripe Sigma will run SQL over all of it if you want the numbers in a table.
Stripe stops at the point where you ask why anything happened. It sees a subscription that cancelled on the 14th. It does not know that the customer opened three tickets about failed CSV exports in the ten days before that. It sees an account whose usage-based charges dropped by half. It does not know that a regression in the API rate limiter was the cause, was found on the 3rd, and shipped a fix on the 9th.
Stripe holds outcomes. It does not hold causes, because causes are not payment events.
Why the gap is structural, not a missing report
Here is the part that matters, and it is not either company's fault.
Half of this evidence is a record. A Stripe subscription is a row: customer ID, price ID, status, amount, currency, current period end. It has fixed fields with fixed types. Any tool that connects to Stripe can read it, filter it, sum it, and chart it, because the shape is known in advance.
The other half is a sentence somebody wrote. The real content of a Linear bug lives in its title and description and comment thread: "checkout hangs on the second card attempt when the billing address autocompletes", "only reproduces for orgs with SSO enabled", "affects the export path, not the UI". Severity is a judgement rendered in prose. Blast radius is a judgement rendered in prose. The label bug tells you it is a bug. It does not tell you whether it was cosmetic or whether it silently broke invoicing for a subset of accounts.
This is why the join is not a reporting feature Linear or Stripe forgot to build.
Automation platforms like Zapier and Make are excellent at this pair, and they are trigger based by design. A Stripe payment fails, so create a Linear issue. A Linear issue moves to Done, so post to Slack. That model moves records forward from the moment something happens. It is the wrong shape for a question that looks backwards at eleven issues already closed and asks what they had in common, because there is no trigger in the past to hang the work on.
BI tools have the opposite constraint. Power BI Copilot and Looker Conversational Analytics will both answer questions in plain language over a warehouse, and they will do it well. They read databases and modelled sources. Stripe's side lands in a warehouse cleanly, because it is already rows. The Linear side lands as a description column full of paragraphs. A BI tool can count those rows. It cannot read what they say, so the severity judgement, the one thing that decides whether an issue matters, is outside what the tool can see at all.
| Question | Linear alone | Stripe alone |
|---|---|---|
| What broke, and when was it fixed | Yes | No |
| Who was affected, by name | Sometimes, via linked support requests | No |
| What that customer pays | No | Yes |
| How serious the bug actually was | In prose, not in a field | No |
| Revenue behind the fixed bugs | No | No |
What the join actually requires
Two things, and only one of them is mechanical.
The key is the customer identity, and in practice it is the email domain or the organisation name. Support tickets linked to a Linear issue carry a requester email. Stripe customers carry an email and usually a company name on the account. ops@northwind-logistics.com in a Linear customer request maps to the Stripe customer whose email shares that domain. This is fuzzy in the ordinary ways: personal Gmail addresses on self-serve accounts, one company billing under a parent entity's name, a customer whose Stripe record uses accounts-payable@ while every ticket comes from an engineer. It is matching work, imperfect, but it is matching work.
The judgement call is the other half, and matching cannot do it. Given an issue titled "webhook retries drop after 3 failures instead of 5", somebody has to read the description and the comments and decide: did this affect paying customers, or was it caught in staging. Did it affect all accounts on a plan, or only those using a specific feature. Was the customer who filed it the only one hitting it, or the only one who bothered to say so. That answer is in the prose. No field holds it, and no join produces it, because it does not exist until someone reads.
How you would answer it with Skopx
Skopx connects to nearly 1,000 SaaS tools, Linear and Stripe among them, and reads across them in one pass. The sentence you type is close to the sentence you would have said out loud:
Look at every Linear issue labelled bug that moved to Done in the last 7 days. For each one, read the description and comments and tell me whether it affected customers in production. Where a customer or support request is attached, match it to Stripe by email domain and give me their plan and MRR. Rank by revenue at stake and show me your reasoning per issue.
What comes back is eleven rows with a paragraph behind each. Three issues turn out to be internal, caught before release, no customer exposure. Two hit self-serve accounts only, about $90 of MRR between them. One, the webhook retry regression, affected four accounts on annual plans totalling $31,000, and only one of them ever filed a ticket. Every claim links back to the Linear issue and the Stripe customer it came from, so the support lead can click through and check any line before forwarding it.
If this is a question you ask every week, the same sentence in chat builds a standing console for it. You describe the view and Skopx assembles an internal app that reads both sources live: fixed bugs from the last cycle down the left, matched paying accounts and their MRR down the right, a revenue-at-stake total at the top, and the severity reasoning expanded under each row. It reads, and it can act on a click. A button on a row that opens a follow-up Linear issue for the accounts still exposed, with a confirmation step before anything is created. That is the only write, and a person has to press it.
What this does not do
It does not store anything. There is no new database of bug-to-revenue mappings accumulating on the side, no forms, no scheduled runs, no alerts that fire when revenue at stake crosses a number. Every load reads Linear and Stripe fresh. Close the console and nothing persists but the sources themselves.
It does not fix your identity problem. If your Stripe customer records have no company names and your support tickets come from personal addresses, the match rate will be poor, and the honest output will say so rather than guess. Fixing that is data hygiene work in Stripe, not something a reading layer can invent.
It does not know about customers who never told you. A bug can affect two hundred accounts and produce one ticket. The analysis is bounded by what was written down, and it should be read as a floor on impact, not a measurement of it.
And the severity reads are judgements, made from prose, which means they can be wrong the same way a person skimming the same thread can be wrong. That is why every row carries its citation. The point is not to remove the reading. It is to do the first pass across eleven issues and two systems in a minute rather than an afternoon, and to show its work so you can disagree with it.
Skopx is $16 per seat per month on Team, with 2.3 million AI tokens included, or $5 for Solo. SOC 2 controls are in place.
Skopx Team
The Skopx engineering and product team