Skip to main content

#506 Great Writing is Rewriting

Friday Ship #506 | August 28th, 2026

A picture of hand-written manuscript, heavy with editing marks by other people

This week, we continued to focus on the refactoring of several core subsystems in our code base.

“There is no great writing, only great rewriting.” The line is usually attributed to
Justice Louis Brandeis, who apparently put his opinions through dozens of drafts before
he was satisfied with them. Writers know the feeling. The first draft exists to find out
what you’re trying to say. Every draft after that is for saying it.

Code is the same, and we have been rewriting a lot of it.

For the past several weeks we’ve been in the middle of re-architecting two of Parabol’s
most fundamental sub-systems: how we integrate with other tools, and how we handle the
credentials that make those integrations work. None of it shipped a feature you can see.
All of it changed what we’re able to build next.

Why we decided to refactor code now

One of the goals of this Shape Up Cycle was to make some changes to Parabol in order to encourage greater interaction with Parabol’s tasking and task integration system. The designs were all ready for implementation but when it came time to get started writing code, it became clear that the changes were going to be much more complicated and messy due to debt tech that had accumulated over the years.

Rather than make a bad problem worse, we decided to make the difficult decision to de-prioritize the cycle goal in order to focus on paying off tech debt.

What “rewriting” actually looks like

Parabol connects to six task services — Jira, Jira Data Center, GitHub, GitLab, Azure
DevOps, and Linear. Until this month, none of the code knew that. There was no list.
Instead, roughly forty separate switch statements scattered across the server and the
client each re-enumerated the six services by hand, and each one was free to get the list
slightly wrong.

That’s not a metaphor for tech debt. That’s the literal shape of it: forty places that had
to agree with each other, maintained by people who had to remember all forty.

The rewrite has gone out in four passes, deliberately boring on purpose:

Contracts and registries. The first pull request added 709 lines and deleted two. It
defined what an integration is — a small required core plus optional capabilities like
issueCreate, issueSearch, and estimatePush — and gave each service one definition
file. It also added a conformance test suite that every service has to pass. Nothing
consumed any of it. The running system was completely unaffected. That was the point:
build the new road before diverting any traffic onto it.

Shared interfaces. The second pass made our GraphQL schema rich enough to render an
integration generically, rather than asking the client to check which service it was
looking at and branch. 84 files.

Auth unification. The third pass, merged Tuesday, moved Jira and GitHub off their
bespoke legacy auth tables and onto the same provider model every other integration
already used. 165 files, and six legacy mutations retired. The old tables are still there
— we keep them for one release so we can roll back — but nothing writes to them anymore.

Dispatch collapse. The fourth pass is in review as I write this: 137 files that replace
the remaining per-service branching with registry lookups.

Roughly ten thousand lines added, nine thousand deleted, and the app does the same thing it
did before. Almost.

Rewriting finds the bugs you weren’t looking for

Here’s what surprised me. We did not set out on a bug hunt. We set out to reorganize. But
when you force six implementations of the same idea to stand next to each other and satisfy
the same interface, the ones that were quietly wrong have nowhere left to hide.

A partial list of what fell out:

  • GitLab tasks were labeled ‘GitHub’ in meeting summary emails. One branch, one wrong
    string, shipped for who knows how long.
  • CSV exports were inconsistent by service. Three services prefixed story titles with
    the issue key; the other three silently fell back to raw task text. Nobody chose that. It
    was just where six independent code paths had drifted to.
  • GitHub searches longer than 256 characters hung forever. GitHub’s API caps search
    strings at 256 characters. We didn’t know, so we showed a loading skeleton and waited.
    Now you get an immediate, specific error message.
  • Linear repositories were being evicted from caches whenever any integration was
    disconnected.
  • Failed token refreshes looped instead of logging the failure and marking the
    connection inactive.

Not one of these was on a bug list. Several had been in production for a long time,
generating exactly the kind of low-grade friction that users are more likely to work around
than report. We found them by rewriting.

That’s the thing about a second draft. You don’t reread your own sentences to check for
typos. You reread them and discover you never actually made the argument you thought you’d made.

Rewriting teaches the machines, too

We made a version of this argument back in Friday Ship #503, when we tore out a decade of deprecated CSS-in-JS across 900 files. The reason wasn’t nostalgia for clean code. It was that frontier models learn what “the way we do things here” means by reading your codebase, and a deprecated pattern that appears in four hundred files reads as a house style rather than a mistake.

“Add Asana as a task integration

Against the old architecture, that instruction expands to: find all forty dispatch sites,
add a case to each, update ten hand-maintained registration lists, create a sixth copy of a
field-mapping table, and add a seventh near-identical mutation. An AI agent will find
thirty-seven of the forty. So will a human. The thirty-eighth becomes next year’s “GitLab
tasks are labeled GitHub.”

Against the new architecture, the same instruction expands to: write one definition file
implementing a documented interface, in a directory that exists for exactly that purpose,
and make the conformance suite pass.

And then, eventually, it gets faster

The third benefit is the one I want to be honest about, because we haven’t earned it yet.

When six services each have their own code path, every optimization has to be implemented
six times, and in practice it gets implemented once or twice and the rest drift. Caching,
batching, retry logic, rate-limit handling, connection reuse…all of it multiplies by the
number of paths.

We’ve had a taste of what convergence buys. The dispatch-collapse work replaces five
separate per-service token refreshers with a single one, and a seven-way switch for building
OAuth clients with one function. On the schema side, five copies of the dimension-field-
mapping table are slated to become one, and six near-identical mutations become one.

But those are simplifications, not yet speedups. The real prize is further out: when there’s
one path for “fetch this team’s issues,” it becomes worth the effort to make that path
genuinely fast, because every service benefits at once.

It’ll be sweet to live in that future.

Metrics

August continues to be August. Our European users are on holiday, our meeting volume is down 7.7% week over week, and monthly actives slipped 2.5%. This is the seasonal pattern we’ve noted in every Friday Ship this month, and we expect the usual September rebound as teams come back to their desks.

One number worth watching: total registered users sits at 699,621. Barring something
strange, we’ll cross 700,000 next week.

This week we…

…reduced the permissions we request for Google Meet transcription. Following last week’s scramble to fix Google’s API change, we took the opportunity to ask for meaningfully less access than before.

…upgraded the AI model behind Parabol’s AI-assisted features.

…added CodeQL static analysis to our continuous integration, so security issues get
caught before review rather than after.

…upgraded the AI model behind Parabol’s AI-assisted features.

…fixed a persisted-query error that could interrupt clients running an older bundle
after a deploy.

Next week we’ll

…continue producing Team Health. We’ve already released an end-to-end experience behind a feature flag, but we’re keen to meet the features we’ve scoped in as minimally viable.

…merge several Pull Requests we have in flight.

Jordan Husney

Jordan Husney

Jordan is Parabol’s CEO and Head of Product. He was previously a Director at Undercurrent, where he advised C-Suite teams of Fortune 100 organizations on the future of work. Jordan has an engineering background, holding several patents in distributed systems and wireless technology. Jordan lives in Minneapolis, MN.

All your agile meetings in one place

Run efficient meetings, get your team talking, and save time. Parabol is free for up to 2 teams.