
I spent 8 years running a team that built software for clients, and this is still the mistake I think about the most.
We put together and signed a SOW with a client for a 6-month build. Pretty standard stuff: a scope doc, milestone schedule, a signature.
That 6-month build went on for a year and a half. Priorities moved. The client asked for things that weren't in the original scope and we built them in good faith. We prided ourselves on understanding what they actually needed, not just what the document said. Some things in the SOW quietly stopped mattering and we dropped them, with everyone's blessing at the time.
All of it was agreed. None of it was agreed anywhere durable. It lived in biweekly meetings, Slack channels, and email chains.
At the end, the client went back and read the SOW.
And from where they sat, we hadn't built what we agreed to. Things on the list weren't there. Things that were there weren't on the list. They were genuinely frustrated and I couldn't really argue, because the record of what we were supposed to be building had been wrong for at least 5 months.
The part that still bothers me is that a big share of that gap was their own requests. They had asked for those changes. They'd just forgotten. Reconstructing it meant digging through a year and a half of Slack, emails, and meeting notes, which is miserable, and doing it in front of a client is indistinguishable from making excuses.
We finished the project. The relationship didn't survive it.
Here's what I took from it:
The problem was never scope creep. Scope creep is the nature of software development. You can't spec 18 months of build in a document written before day one, and what gets delivered always drifts from what was signed.
And yes, we should have amended the SOW as we went. That's on me. But we'd treated it the way I think most teams do: a high-level foundation to start from, not a document you reopen every sprint. Each individual change felt too small to justify contract paperwork. By the time the drift was big enough to formalize, it was everywhere, and formalizing it would have meant reconstructing a year of decisions first.
The real problem was that there was no maintained record of what we were building. Meeting notes, email chains, Slack, Gantt charts, a Jira board — we communicated progress multiple times a week. But all of those show where things stand now. None of them show how the scope changed, when, or who asked. Two different memories of a year and a half, and no tiebreaker.
So I built the version I wanted. Here's how it works:
You upload the signed SOW and it becomes a scope record: a structured list of every deliverable in the agreement, each with a status, instead of a wall of contract text.
As the project moves, you keep the statuses current. Update them in a couple of clicks, or let it read your repo and draft the week's updates from your real commits and PRs, and you just confirm or reject. Upkeep is a review, not a writing task.
When you publish, it saves a version. That's the history I was missing: what changed, when it changed, and what was added beyond the original scope. The thing I needed that day, instead of scrolling Slack.
Your client gets one read-only link, no login. It opens on what's changed since they last looked, laid out cleanly enough that they actually read it.
Live example if you want to see the artifact, no signup: https://scopediff.com/demo/cobalt
What I'm genuinely curious about, since I only have my own experience here:
How do you send client updates today? Email, Slack, a weekly call? And do your clients actually read them?
If a client questions something months later ("when did we decide this?"), what do you actually do? Dig through Slack like I did, or do you have something better?
And the stuff you build that was never in the SOW (the small favors, the quick asks that never felt worth writing down): does that get recorded anywhere, or does it just disappear?
I like this idea. Curious, what does the data entry process look like? ie. Are updates manually entered or does it also support integrations with other tools eg. project boards, GitHub, ...
Thanks! The initial entry is automatic. You upload the signed SOW and it parses into a structured list of deliverables, each with a description, acceptance criteria, and a status. For updates, you can either change a status directly (couple of clicks) or connect GitHub and it reads your commits and PRs, drafts the weekly update, and you confirm or reject. Project boards like Jira aren't integrated yet. GitHub came first since the repo is closer to ground truth than a board.
You can check it out at https://scopediff.com if you're curious.
Which project boards do you use that would be valuable to integrate with? Helps me figure out what could be next.
Starting with Github makes sense.
I think any of the ones popular with freelancers, or small agencies would do. I use Trello, but have also used the likes of notion and Jira in the past.
I would keep contractual scope separate from the current working scope.
If every accepted change simply becomes the new baseline, the history remains clean, but the team can lose sight of what is still contractually outstanding. I would show three simultaneous states: currently agreed work, delivered but unapproved work, and original obligations that have not been formally superseded.
Repository activity can draft movement between those states, but it cannot approve it. That makes the record a decision surface rather than just a better project timeline.
This is close to the exact model I designed around! The way ScopeDiff works: v1 is the parsed SOW and it's immutable. Every later version diffs against history, so nothing silently becomes the new normal, and additions get flagged as 'beyond the original scope' rather than absorbed. And the GitHub scan can only propose status changes. A person confirms every one before anything publishes.
The "original obligations that have not been formally superseded" state is where you're ahead of me. This can be answered through the version history, but there's no standing view for it. Change orders are on the roadmap but not built yet. I've been careful with that one because the record isn't the contract. Only the signed doc binds, so "superseded" has to mean both sides approved it, not just an edit.
One thing I'm thinking about from your comment -- the client view is deliberately calm and meant to answer "are we on track" in a few seconds. How would you surface those three simultaneous states without it turning back into a project board clients don't want to read?
I’d model this as two axes rather than three competing workflows: operational state (agreed, delivered, accepted) and contractual status (original, added, formally superseded).
Then the client view can remain one calm summary:
On track · 2 items awaiting acceptance · 1 original obligation needs a signed change order.
Only non-zero exceptions need to expand. The complete history and GitHub evidence can sit behind each number, while the default view answers “what needs my decision?” rather than showing every task.
That also avoids implying that the record is the contract. The contractual status changes only when signed approval exists; operational work can keep moving without silently rewriting the original baseline.
The line that matters here is "all of it was agreed, none of it was agreed anywhere durable." I ran client work for years and the failure was never the change itself — it was that the change was approved in a medium with no index. Slack is a great place to decide things and a terrible place to remember them.
The cheap version of the fix that worked for me was a single change log in the shared doc: one row per change, date, who asked, what it replaced or displaced, and a one-line schedule impact. Two minutes after the call, pasted into the same thread so nobody has to accept a summary they didn't see. The magic isn't the format, it's that "what this displaces" forces the tradeoff into the open while it's still a small conversation. Most drift happens because additions get an explicit yes and the things they quietly push out never get an explicit no.
The other piece I'd add: send a rolled-up scope state at every invoice, not every sprint. Clients don't read sprint updates, but they read the thing attached to a bill, and it means the record gets acknowledged monthly rather than reconstructed at month 18. Your point that reconstruction in front of a client is indistinguishable from excuses is exactly right — that's why the acknowledgment has to happen while it's still boring.
Thanks for this! Both of these are better than what we were doing. The change log especially. Additions getting an explicit yes while the things they pushed out never got a no was exactly what happened to us.
One question: could teammates keep the change log to the same standard, or was it mostly you? That's where every system I tried fell apart.
And a selfish ask: what I built is basically that change log made automatic. It versions the scope on every publish and has the client acknowledgment built in. If you ever look at the demo in the post, I'd honestly like to know what your manual setup did that mine doesn't. You've run this longer than most of the people I've talked to.
The strongest part is treating scope as a living record rather than a document that gets forgotten after signing. Versioned changes with a clear history seem much more useful than trying to reconstruct decisions from a year of Slack and email later.
The tiebreaker problem is subtler than "we didn't keep a record" — it's that even a record has to be provably unaltered to actually settle a dispute months later. A change log or a versioned scope doc solves "nobody wrote it down." It doesn't fully solve "the client trusts my copy of the record." If the only place the history lives is a system you control, the honest question a client can still ask is "when was this actually updated" — not "was it updated."
We ran into a version of this building Incipite, a proof-of-authorship tool: the record itself needs a timestamp nobody involved can dispute after the fact, or you've just moved the trust problem from "what happened" to "when did you say it happened." For scope specifically, that might mean the published version carries some kind of external, tamper-evident timestamp — not to replace the change log, but so "I updated it that week, not after the fact" is provable rather than asserted.
Yeah that's interesting. We try to handle that in ScopeDiff through versioning. Once a version is published it can't be edited or backdated, so the timeline isn't something that can be rewritten after a dispute starts. The client can also acknowledge a version (there's an input form for them to add their name and 'acknowledge' each update), which timestamps it.
That's a clean way to close the gap. Publish-then-frozen answers "when was this updated," and the acknowledgment step answers the other half, since a version nobody looked at isn't much better than one nobody wrote down.
Curious what happens when a client never acknowledges a version. If the record's authority depends partly on their action, there's a gap between "published" and "confirmed" a determined client could still exploit. Not a knock on the approach, just the edge case I'd want answered before I trusted it under pressure.
The read-only client link with no login is the detail I'd actually use. Every scope tool I've tried assumes the client will log into a portal to check status, and they never do - they read email and Slack, nothing else. A link that just opens to "what changed since you last looked" matches how clients actually behave. Following the other question here: for the changes that never touch a commit, would a lightweight "log this decision" quick-add (even just pasting the Slack message) beat trying to infer it automatically? Seems like the zero-commit changes are exactly the ones most likely to get argued about later.
You can do most of this today by pasting the Slack message into the notes flow, and it'll propose the register change for you to confirm. Keeping the original message attached to the change as evidence does not exist yet, but is a good idea that I'll add to the backlog. Thanks!
The bit that jumps out at me is that all your durable records were "now" records. Jira, the board, even Slack search, they show state, not the decisions that got you there. Clients never ask for that trail until the one day they do.
You said it yourself though, the SOW should've been amended as you went. That's the whole game. Signed scope, and every change agreed against it before anyone builds. The reason people skip it is they think a change request means cost and a week of paperwork. It doesn't. Half of mine were zero-cost, one line: "swap X for Y, no change to price or date, agreed 12th." Ticked off by both sides in the same thread and done.
What that buys you isn't just the record. It's the explicit no. Most drift happens because the additions get a yes and the things they quietly push out never get an actual no. A CR forces that tradeoff into the open while it's still a two-minute conversation, not a month 18 argument.
The one I never solved is the 20-minute Thursday favour that nobody thinks is worth a CR. If your GitHub scan catches those from the commits, that's the useful part, because it's the drift no human writes down.
Quick question: how does it handle a change with no commit behind it? A lot of what displaced our scope was "don't build that, do this instead", a decision that never touches the repo.
To answer your question - the scope record is editable fully manually (few clicks to change status, update things as out of scope, etc.) and you can also paste in rough notes from a call and it'll propose the register changes for you, same way the Github scan does from commits.
The "no durable record" part is the real killer — we hit the same wall. Fix: one living scope doc updated every sprint, client replies "agreed" in the same thread. Weekly diffs instead of reconstructing 18 months of Slack at the end. Scope drift is fine; invisible drift ends relationships.