Skip to content

docs: add ADR 0047, move the SDK into the clients monorepo - #861

Open
coroiu wants to merge 8 commits into
mainfrom
docs/adr-0035-product-repository
Open

coroiu wants to merge 8 commits into
mainfrom
docs/adr-0035-product-repository

Conversation

@coroiu

@coroiu coroiu commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

🎟️ Tracking

No Jira ticket. This ADR came out of a cross-repo breaking-changes investigation.

📔 Objective

Adds ADR 0047 (status: Proposed): move the SDK into the clients monorepo, so the SDK and the clients build together and a change to the SDK lands in the same commit as the client updates it requires.

The ADR records the problem (breaking changes serialized through the npm/Maven publish pipeline, worsening as more teams contribute to the SDK), the options considered (do nothing, additive-only, coordinated merge gate, versioned SDK releases, product repository, monorepo), and the decision with its consequences and plan (including keeping android and iOS out of the monorepo, with work to improve the mobile boundary), and follow-ups (the server's dependency on the SDK). The product repository is recorded as a considered alternative we can revisit if a second repository is ever warranted.

Proposed for discussion.

If you prefer you can also look at the hand-written version/starting point

Input for ADR

Ever since we introduced more teams into the SDK we've had a continuous challenge: Breaking changes.
Developers want to move fast, and the best way of doing that is using breaking changes, because of this
we've continuously tried to find ways of enabling this. An example of this is the breaking changes detection
workflow which coordinates with the clients monorepo to build the latest main clients commit against
SDK PRs (this actually started with a purely static analysis of the TypeScript types on the SDK repo alone).
The goal of this workflow was to encourage and guide developers to have fix PRs for the breaking changes reviewed and ready for merge. Unfortunately, it hasn't achieved this to the degree that we were hoping for as we've seen breaking changes increase even with the workflow. I argue that not doing anything is not an option, as the situation will continue to deteriorate as more and more features and development moves to the SDK.

What is the problem with breaking changes?

The issues with breaking changes can be traced back to the SDK living in a separate repository and pulled into the clients as a published NPM package because of how it collapses the flow into a serialized delivery pipeline. In other words, it creates a bottleneck.

<style> .rail { fill: none; stroke: #1a1a1a; stroke-width: 3; stroke-linecap: round; } .dash { fill: none; stroke: #1a1a1a; stroke-width: 3; stroke-linecap: round; stroke-dasharray: 2 14; } .npm { fill: none; stroke: #888; stroke-width: 2; } .label { fill: #1a1a1a; font-size: 30px; } .npmlabel { fill: #888; font-size: 26px; } </style>

SDK
NPM
Clients

Think of this in another way, every breaking change in the SDK adds to a queue of fixes that then need to be merged on the clients repo, in the same order as they were merged in the SDK. So if Team A merges a breaking change into the SDK, then every other team needs to wait for the corresponding clients fix until they can start pulling in SDK features merged after the breaking change.

I did some research by asking Claude to look at the previous 12 months. When reading it it's important to note that, while research may show a median breaking change on clients only taking a couple of hours to fix, that is a couple of hours where nobody else can merge. It's one thing to look at it from the perspective of absolute time, and another from the perspective of total person-hours affected. Here is what the research found: [claude: insert your research and conclusions here].

Side-note on Server - SDK breaking changes

These also present a challenge because of how we automatically generate API bindings from server commits.
However, this relationship is a bit different and can be split into two parts:

Compile time breaks

These are the ones that are most similar to the SDK - Clients breaks. If a breaking change is merged on the server then
a fix needs to be merged on the SDK before any additional changes after the break can be pulled in. The research shows [claude: insert research here (these are not as common)].

Run time breaks

These come from the fact that running SDK instances are not physically tied to a server version (SDK is client side, Server is server), meaning that the SDK needs to combine static type safety with the ability to handle unknown data coming from an unknown server version. This is fundamentally different from SDK - Client where the SDK is physically bundled with each client at compile time. We need to follow-up on this separately, but it is just a matter of implementing better support for these scenarios, and not an architectural re-design.

Alternatives considered

Coordinated merge gate (cross-repo/meta-repo)

Keep the repos separate, but link a breaking sdk-internal change to its clients/mobile fixes and merge them together as one gated change set, essentially a distributed atomic merge. This would probably be implemented as an extension of the automations we already have.

Advantages

  • Same guarantee as the monorepo (a breaking change can't merge without its fixes).
  • Builds on automation we already have.

Disadvantages

  • Cross-repo merge coordination is complex to build and maintain (ordering, timing, separate CIs).
  • There's a limit to how fast a merge can happen because you have to wait for:
    • SDK to be built on main
    • SDK to be deployed to package repository
    • Clients to update PR with correct SDK package.json version
    • Clients PR to re-build CI after change
  • Does not remove the bottleneck: What happens in the time after an SDK PR is merged and before the Clients fix is merged? Do we block additional PR merges? For how long?

Ban breaking changes

Exactly what it says, ban breaking changes and force every update to be backwards compatible.

Advantages

  • Effectively resolves all issues.

Disadvantages

  • Adds a bunch of overhead needed to maintain backwards compatibility (and then cleaning it up).
  • "a bunch of overhead" really doesn't do it justice, the time spent on this can increase exponentially with every change because of how strict rust is.

Do nothing - improve what we can

Continue allowing breaking changes, don't move any repositories, don't add any gates.
This actually turns into a collection of smaller solutions, some examples:

  • Reduce breaks by changing our interface, examples:
    • Let the SDK own construction (builders/constructors) so added fields don't break callers
    • Make new fields optional by default,
    • Minimise the fine-grained types clients have to construct or exhaustively match
  • Insulate clients behind a facade layer

Advantages

  • Iterative improvements, no big work effort.
  • Not disruptive

Disadvantages

  • Doesn't address the bottleneck
  • Will still cause breaking changes

Monorepo

The solution that was chosen for jslib: move the SDK into the clients repo.

Advantages

  • Breaking changes can no longer be merged without fixing them because that would break CI.
  • Existing AI tools automatically get the entire context, e.g. when reviewing.

Disadvantages

  • A lot of upfront work to migrate
  • Bigger repository, slower git, CI, etc.

Product repository

Similar to the monorepo but taking Bitwarden's ambition of becoming a true multi-product company into account. The core idea here is: Move closely related code together to address the most frequent breaking changes but keep the core infrastructure separate and available for other products. There is a fundamental assumption being made here: Products will not need to share application logic at the SDK level.

Advantages

  • Same as for monorepo.
  • Prepares us for extracting products from the clients repo, e.g. Secrets Manager.

Disadvantages

  • Also same as for monorepo.

Solution chosen: Product repository

This solution combines a few key traits that makes it stand out as the best solution: Fastest development time, smallest maintenance cost. It's a one time effort, one that we've done before with jslib, which results in an environment that lets us write any type of changes as often as we want without having to worry about compatibility.

Some additional points [claude: try to merge these into flowing text with the section above]:

  • What about a public SDK? The feasibility of that is not affected by this change. We would simply treat it as another client/artifact that we release.

Open question

  • Do we decide what to do with mobile now? Or leave it as a standalone repo for now?

Follow ups

  • The SDK and Server repositories currently have a circular relationship. Moving to a product repo would cause Server to depend on Clients instead. This could be solved by moving towards a true product repository in which case the Server and Client components would naturally depend on their common SDK/Rust core. If this is compatible with MSA on the backend has not been explored.

…tory

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coroiu coroiu added the ai-review Request a Claude code review label Sep 9, 2026
@github-actions github-actions Bot added the adr label Sep 9, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Deploying contributing-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 389da1c
Status: ✅  Deploy successful!
Preview URL: https://e1d9e6c2.contributing-docs.pages.dev
Branch Preview URL: https://docs-adr-0035-product-reposi.contributing-docs.pages.dev

View logs

@github-actions

github-actions Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

This PR adds one file, docs/architecture/adr/0047-move-sdk-into-monorepo.md, a Proposed ADR to move the SDK into the clients monorepo. The new head commit 389da1c only merges origin/main into the branch. The ADR file is byte-for-byte the same as at f88c7d3, the commit the previous review approved, and the merged commits (#849, #878, #888) don't touch the PR's diff. ADR number 0047 is still unique: the highest other ADR on main is 0038, so the merge caused no collision. Lint, Validate, and Aikido pass, and Build is still running.

Code Review Details

No new findings. One thread is still unresolved but outdated: the inline-SVG diagram thread. The SVG has since been removed and no diagram remains, so nothing needs to change.

Comment on lines +21 to +46
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 560" fontFamily="ui-sans-serif, system-ui, sans-serif">
<text x="120" y="70" fill="#1a1a1a" fontSize="30">SDK</text>
<text x="472" y="66" fill="#888" fontSize="26">NPM</text>
<text x="800" y="70" fill="#1a1a1a" fontSize="30">Clients</text>

<line x1="500" y1="110" x2="500" y2="490" fill="none" stroke="#888" strokeWidth="2" />

<g fill="none" stroke="#1a1a1a" strokeWidth="3" strokeLinecap="round">
<path d="M70,120 L290,120 C360,120 380,300 450,300" />
<path d="M70,165 L240,165 C330,165 370,300 450,300" />
<path d="M70,210 L190,210 C300,210 350,300 450,300" />
<path d="M70,480 L290,480 C360,480 380,300 450,300" />
<path d="M70,435 L240,435 C330,435 370,300 450,300" />
<path d="M70,390 L190,390 C300,390 350,300 450,300" />
<path d="M70,300 L450,300" />
<path d="M550,300 C650,300 700,210 810,210 L930,210" />
<path d="M550,300 C630,300 660,165 760,165 L930,165" />
<path d="M550,300 C620,300 640,120 710,120 L930,120" />
<path d="M550,300 C650,300 700,390 810,390 L930,390" />
<path d="M550,300 C630,300 660,435 760,435 L930,435" />
<path d="M550,300 C620,300 640,480 710,480 L930,480" />
<path d="M550,300 L930,300" />
</g>

<path d="M450,300 L550,300" fill="none" stroke="#1a1a1a" strokeWidth="3" strokeLinecap="round" strokeDasharray="2 14" />
</svg>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ DEBT: Diagram is inline SVG rather than Mermaid, and carries no perspective caption

Details and fix

The diagram standard adopted in ADR-0033 states rule 1 as "Diagrams are Mermaid source text, nothing else" and rule 4 as "Every diagram carries a perspective caption: audience, intent, and scope", attached as an italic blockquote below the diagram. This is the only inline <svg> in docs/; the two remaining SVGs are the autofill assets that ADR-0033 records as a tracked deviation until replacements ship, and this site is meant to be the standard's reference implementation.

There is also a concrete rendering consequence: the strokes and text are hardcoded to #1a1a1a (and #888). docusaurus.config.js sets no colorMode override, so the default dark-mode toggle is available, and in dark mode near-black lines on the dark background make the diagram effectively invisible. Mermaid inherits the site theme automatically, and markdown.mermaid is already enabled.

A flowchart LR with the contributing teams fanning into a single publish node and out to the consuming clients would carry the same "serialized pipeline" message, in theme-aware, diffable source.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be converted to mermaid.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't, I'll probably end up removing it

@Hinton Hinton left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe we are currently in a place where we can efficiently deal with separate repositories for products. The SM SDK constantly lags behind "sdk-internal" and without process changes where "core" is forced to be stable with longer upgrade cycles we're just moving the pain from sdk-internal <-> clients to sdk-core <-> sdk-product.

If we start banning breaking changes in sdk-core, and have it adopt a regular release cadence, and we support past + current release with bug fixes then that could work. But if sdk-core continues with frequent breaking changes you still have to play catch-up. And looking at the current velocity I would expect the turmoil in "core" to continue.

Anything preventing us from taking along core into clients? It already hosts the SM clients, and there is no near term plan to change that. Rather we would be forced to put the SM SDK there anyway to maintain the web experience.

@coroiu

coroiu commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

@Hinton

without process changes where "core" is forced to be stable with longer upgrade cycles we're just moving the pain from sdk-internal <-> clients to sdk-core <-> sdk-product.

True, and

If we start banning breaking changes in sdk-core, and have it adopt a regular release cadence, and we support past + current release with bug fixes then that could work.

is exactly how the proposal addresses this:

The core infrastructure crates (for example bitwarden-core, bitwarden-crypto, bitwarden-encoding, bitwarden-state) stay a separately published, versioned library

  • Core-layer breaks still cross a published-package boundary between the core library and the
    product repository. This is smaller than today because the core churns less than the application
    layer, but it is real. The plan addresses it with explicit versioning of the core.

but maybe the text doesn't fully elaborate on exactly what this means and the consequences. I'll update the doc!

looking at the current velocity I would expect the turmoil in "core" to continue.

Revisiting the data it seems like you have a point here. According to some new data I had Claude assemble there are significant changes in the core crates. The difference is that we would be minimizing the amount of teams creating these breaks to platform/core teams. Having a stable API for crypto can also be seen as desirable.

Anything preventing us from taking along core into clients? It already hosts the SM clients, and there is no near term plan to change that. Rather we would be forced to put the SM SDK there anyway to maintain the web experience.

Not technically, no. My main reason for recommending the product-repository approach was mainly to more closely follow MSA architecture in general. This gathers closely related code and allows separation of code that is unrelated, organizing code by verticals instead of horizontals if you will.

Of course, the initial proposal I brought up was monorepo because that also solves our current problems. I'm just not sure if it's compatible with how the organization is moving as a whole. Of course, nothing gets written in stone either way and we can always split out core later

@coroiu
coroiu requested a review from MGibson1 September 16, 2026 14:09
@Hinton

Hinton commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

Since clients will most likely host PM, SM and AC for the foreseeable future it's unclear to me what we gain by this separation. There are no current plan to break up existing repositories, hence all clients will be required to consume the exact same versions of "core" libraries near/mid term. To me it seems like we want to spend a lot of effort for a hypothetical scenario which may or may not happen.

If all the platform teams are alright enrolling in a release cadence, commit to stable APIs and maintain multiple parallel versions with bugfixes then I have no objection.

coroiu and others added 2 commits September 22, 2026 16:40
@coroiu coroiu changed the title docs: add ADR 0035, move SDK application crates into a product repository docs: add ADR 0035, move the SDK into the clients monorepo Sep 22, 2026
@coroiu

coroiu commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor Author

@Hinton I've taken peoples feedback into account and revised the ADR to propose the original monorepo approach which we had all agreed on. I left product repo as a considered but rejected alternative 👍 Thank you for you feedback!

If all the platform teams are alright enrolling in a release cadence, commit to stable APIs and maintain multiple parallel versions with bugfixes then I have no objection.

I think there's still a chance we might to exactly this at some point in the future (the ADR was exactly this commitment) but like you say, today it's not a strict requirement, so no need to take on more work that necessary

Comment thread docs/architecture/adr/0035-move-sdk-into-monorepo.md Outdated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread docs/architecture/adr/0035-move-sdk-into-monorepo.md Outdated
MGibson1
MGibson1 previously approved these changes Sep 23, 2026

@MGibson1 MGibson1 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've long been an advocate of monorepos. I think this ADR brings solid data to the table that this will help the web clients, but I do worry about the mobile clients, still

it would be nice to understand if similar headaches exist there while considering this.

@Hinton

Hinton commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

it would be nice to understand if similar headaches exist there while considering this.

They do to a certain degree. Mobile is still disconnected from regular engineering teams though and I'm somewhat concerned what coupling SDK changes would mean. In many ways they are better suited for a stable release schedule with backported bugfixes. I think web clients are less suited for that because a 1 week or 2 week delay would be to disruptive.

@Hinton Hinton left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall positive to this. Mobile is somewhat concerning, what would bringing that into our repository look like. Would we be willing to accept breaking changes to the SDK requirering mobile updates?

Comment on lines +21 to +46
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 560" fontFamily="ui-sans-serif, system-ui, sans-serif">
<text x="120" y="70" fill="#1a1a1a" fontSize="30">SDK</text>
<text x="472" y="66" fill="#888" fontSize="26">NPM</text>
<text x="800" y="70" fill="#1a1a1a" fontSize="30">Clients</text>

<line x1="500" y1="110" x2="500" y2="490" fill="none" stroke="#888" strokeWidth="2" />

<g fill="none" stroke="#1a1a1a" strokeWidth="3" strokeLinecap="round">
<path d="M70,120 L290,120 C360,120 380,300 450,300" />
<path d="M70,165 L240,165 C330,165 370,300 450,300" />
<path d="M70,210 L190,210 C300,210 350,300 450,300" />
<path d="M70,480 L290,480 C360,480 380,300 450,300" />
<path d="M70,435 L240,435 C330,435 370,300 450,300" />
<path d="M70,390 L190,390 C300,390 350,300 450,300" />
<path d="M70,300 L450,300" />
<path d="M550,300 C650,300 700,210 810,210 L930,210" />
<path d="M550,300 C630,300 660,165 760,165 L930,165" />
<path d="M550,300 C620,300 640,120 710,120 L930,120" />
<path d="M550,300 C650,300 700,390 810,390 L930,390" />
<path d="M550,300 C630,300 660,435 760,435 L930,435" />
<path d="M550,300 C620,300 640,480 710,480 L930,480" />
<path d="M550,300 L930,300" />
</g>

<path d="M450,300 L550,300" fill="none" stroke="#1a1a1a" strokeWidth="3" strokeLinecap="round" strokeDasharray="2 14" />
</svg>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs to be converted to mermaid.

Comment thread docs/architecture/adr/0047-move-sdk-into-monorepo.md
@withinfocus
withinfocus marked this pull request as ready for review September 24, 2026 17:18
@withinfocus
withinfocus requested a review from a team as a code owner September 24, 2026 17:18
@withinfocus
withinfocus marked this pull request as draft September 24, 2026 17:21
@withinfocus

Copy link
Copy Markdown
Contributor

Note that this needs renumbering now.

…cope

Add versioned SDK releases as a considered option, decide that android
and iOS stay out of the monorepo with commitments to improve the mobile
boundary, scope the positive consequence to the 18 clients events, and
tighten wording.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coroiu coroiu changed the title docs: add ADR 0035, move the SDK into the clients monorepo docs: add ADR 0047, move the SDK into the clients monorepo Oct 6, 2026
Event counts misrepresent impact, which is better measured in
person-hours. The inline SVG does not follow the Mermaid diagram
standard.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coroiu
coroiu requested review from Hinton and MGibson1 October 6, 2026 10:59
@coroiu
coroiu marked this pull request as ready for review October 7, 2026 08:30

@quexten quexten left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, when we first talked about this I was neutral, but at this point I am (slightly) opposed to a mono repo, at least for the reasons stated in this ADR. It does not solve the problem for mobile or external consumers of our SDK and possibly makes the problem worse by making breaking changes easier / more acceptable within clients.

Further, the problem we had in the first place is simply much less frequent now that the AI team has added the auto-fix workflow. Breaking changes are fairly automatically resolved, so it's not really an issue? On the other end we have a large effort we are doing here, for an unclear outcome.

A 12-month measurement (2025-09-01 to 2026-08-31, read from merged PRs and git history) shows the
shape of the problem:

- **68 forced downstream breaking events**: clients 18, android 28, iOS 22. The earlier six months

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Now that we have the LLM-auto-fix workflow in place, does this argument still hold?

Mots changes are auto-resolved. cc @SaintPatrck

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I spoke with @vvolkgang regarding how this affects mobile and the impression I got was that, while AI does help with minor things, there are still a lot of changes that require human intervention, especially breaking changes that only show up during runtime. As for clients, do we have an AI job there now?

@quexten quexten Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As for clients, do we have an AI job there now?

Yes, we had it sucecssfully auto-resolve some things already.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok nice, I haven't seen it yet and the ADR has taking way to long to get published. I think I'll have to evaluate how it performs before I can comment


- **68 forced downstream breaking events**: clients 18, android 28, iOS 22. The earlier six months
had 25; the recent six months had 43, about 1.7x, with August the peak at 16.
- **About 20% of merges to `sdk-internal` main are breaking**: 149 of 754. Distinct authors per

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are they breaking? Have we investigated that?

To me that suggests the interfaces are bad, which is an issue regardless of the PR management.

@Hinton Hinton Oct 13, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SDK owns the data model, and new features modify the data model. At the moment that causes hard breakages since the conversion is not lossy as that may introduce data loss instead it forces the consumer to decide what to do with the change.

(and a Maven package for android, a Swift package for iOS). Every breaking change in the SDK has to
travel through that publish step before any consumer can adopt the change that follows it. That
turns delivery into a single serialized pipeline: many teams write to the SDK, everything funnels
through one published version, and many teams consume it on the other side. One team's unfinished

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One team's unfinished downstream fix holds up everyone waiting for a later SDK change

Does this still hold true with the auto-fix workflow treated by the AI team?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, codeowners. With timezones that's upwards of a 24h delay assuming the owning team prioritizes review.

downstream fix holds up everyone waiting for a later SDK change.

Put another way: every breaking change in the SDK adds to a queue of fixes that then have to merge
on the clients side in the same order they merged in the SDK. If Team A merges a breaking change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to note, that in one of our slack channels, Brian fairly clearly stated that the SDK's purpose in not only internal teams (our mobile clients and clients), but external users too. This ADR currently ignores this fully, and the solution does not help those users.

The fact is that we make too many breaking changes, because our interfaces are designed ad-hoc for our clients rather than being designed from an "i want to design a stable SDK" perspective.

@coroiu coroiu Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree and also want the SDK to be the basis for something we eventually release as a "public SDK" (I'm not sure if this is actually a company goal yet though), but I don't think an "everything SDK" is how that will look, and that it will instead be released as product SDKs. As an analogy, you don't have a "Google everything API" or "Atlassian API" instead you get APIs for Google Maps, Google Search, Jira, Confluence, etc. Either way, where the code lives does not dictate what we can and can not release

The fact is that we make too many breaking changes, because our interfaces are designed ad-hoc for our clients rather than being designed from an "i want to design a stable SDK" perspective.

Yes, and I think this follows the same principles as the current server APIs, we have an internal API for our clients and we have a public API that follows separate rules when it comes to breaking changes. I envision the SDK doing the same, but I don't want to say "we can't move fast and break stuff because we have a public API that nobody currently uses"

@mzieniukbw mzieniukbw Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it will instead be released as product SDKs

Maybe we should then split the SDK into individual product SDKs ?
Or rather, since PM remains the main offering that have multiple clients, sdk-intenal would only contain PM client, while SM, PAM, (AC?) would either fork or completely separate out.
For "platform" stuff, including crypto, we could have a versioned, stable release, that many product SDKs could consume.

@coroiu coroiu Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An external facing SDK is not currently on any roadmap. Such an SDK would have a very different release cadence and would most likely need to be a wrapper to facilitate LTS.

surface changes (struct and field reshapes, renames, unions, new APIs), and the clients coupling
points are mostly vault, tools, admin-console, and importer code.

The reason to act is not the size of the problem today, it is the trend. The main driver is upstream

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, so here we are saying "We will move sdk to clients, because we recognize we are making breaking changes, and will make more of them, so to make breaking changes less impactful, everything living in one merge is better".

I want to warn about this stance. It does not consider mobile, and external consumers of the SDK. Worse, it encourages the breaking change pattern by making breaking changes more comfortable to the team implementing the feature, without regard for external consumers, or our mobile team.


## Considered options

- **Do nothing, improve iteratively:** keep the repos separate and reduce breaks at the edges (SDK

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have we done a survey as to why breaking changes are breaking? Was the interface designed wrong in the first place? Is this just regular product development?

Can we address this by having guidelines as to what stable APIs should look like?

@coroiu coroiu Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I have https://bitwarden.atlassian.net/wiki/spaces/~62955327d9eae9006f348ae9/pages/3289186311/Breaking+changes+across+repositories and https://bitwarden.atlassian.net/wiki/spaces/~62955327d9eae9006f348ae9/pages/3340435458/SDK+breaking-change+event+log+raw+data as the basis for this PR.

Can we address this by having guidelines as to what stable APIs should look like?

You can go further than guidelines and block breaking changes in CI, at least the ones the static types can detect. That's the "Ban breaking changes" option. However you'd be paying for it by having to carry a backwards-compatible surface, and some changes can't be made additive at all. For example, cipher blob encryption (bitwarden/sdk-internal#1122) made Cipher.name optional, because blob-encrypted ciphers keep the name inside the encrypted blob. It broke clients, android, and iOS, and there was no additive way to do it. Changes like that would still need an explicit, coordinated exception.

You can also re-design interfaces and how objects are constructed, I mention this under Reduce breaks by surface design in the confluence page above:

Shape the SDK's exported surface so it is hard to break: let the SDK own construction (builders/constructors) so added fields don't break callers, make new fields optional by default, and minimise the fine-grained types clients have to construct or exhaustively match. Most breaks today come from clients hand-building SDK input structs.

but by introducing builders you are weakening static typing usefulness:

Oscar: I think another gotcha here is the lack of compile errors, meaning it’s easy to miss setting the field somewhere.
Andreas: Yeah, I feel like whatever we do we’re just pushing the slider back and forth between static safety and dynamic flexibility

@audreyality audreyality Oct 12, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ The analysis in those docs explains the proximate causes of breaking changes. I wonder whether there's a root cause that we're missing?

  • Are folks making mistakes because they're unfamiliar with Rust?
  • Or perhaps because they don't know it's a breaking change?
  • Or is our culture surrounding breaking changes too permissive? Dismissive?
  • Is this an up-front design issue?
  • Is it due to vulns or structural bugs?

The SDK stays its own project inside `clients`, with its own crates, ownership, and published
packages for consumers outside the repository.

The monorepo covers the SDK and the clients. Android and iOS stay in their own repositories and keep

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per above, this will not solve the issue for iOS/Android and possibly will make it worse.


### Positive consequences

- Breaking changes between the SDK and the clients stop being cross-repo events. A change to the SDK

@quexten quexten Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider this bad framing / false. Yes, one client set is prevented from breaking. External clients and mobile will still break, and if we don't change the fact that we are making breaking changes, all we are doing here is enabling more frequent breaking changes, which is worse for both mobile and external consumers compared to now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this stance, I would not consider that we can offer the SDK as something external customers can use.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clients here being the clients in the clients repo, not in the general sense. Yeah, it's confusing, I should probably change this

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if this referred to the clients repo, i would hold the same stance of framing this as a benefit being false framing, specifically because the benfit directly means us encouraging more breaking changes by making them easier.

What I'm saying is that this "benefit" is not a benefit but a downside, when viewed in context.

@theMickster theMickster left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ADR is long and verbose. That is good because it's an important decision because it's moving a key part of our code into another. However, the comprehension fatigue is legitimate. Please consider my notes and any other places where anything is duplicated or overstated.

Otherwise, I support the effort and think this is a great idea for the team and our code health long term!

through one published version, and many teams consume it on the other side. One team's unfinished
downstream fix holds up everyone waiting for a later SDK change.

Put another way: every breaking change in the SDK adds to a queue of fixes that then have to merge

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨/♻️ Please consider refactoring the following sentence because it reads as throat clearing.

Put another way: every breaking change in the SDK adds to a queue of fixes that then have to merge

to

Every breaking change in the SDK adds to a queue of fixes that then have to merge


- **68 forced downstream breaking events**: clients 18, android 28, iOS 22. The earlier six months
had 25; the recent six months had 43, about 1.7x, with August the peak at 16.
- **About 20% of merges to `sdk-internal` main are breaking**: 149 of 754. Distinct authors per

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 / ♻️ Please consider changing this sentence/phrase:

month grew from about 14 to about 29 over the year. More contributors means more breaking changes.

to

month grew from about 14 to about 29 over the year, alongside the growth in breaking merges.

adaptation effort and the branch sitting open between re-bumps, not by waiting for a reviewer, and
per-break approval time cannot be cleanly separated from it. The cost across all three repos is
coordination and adaptation, not code review.
- Keep in mind that this is absolute time and the real cost is person-hours across the blocked

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 / ♻️ Please consider changing this sentence/phrase:

Keep in mind that this is absolute time and the real cost is person-hours across the blocked

to

This is absolute time. The real cost is person-hours across the blocked

per-break approval time cannot be cleanly separated from it. The cost across all three repos is
coordination and adaptation, not code review.
- Keep in mind that this is absolute time and the real cost is person-hours across the blocked
teams. On repositories like `clients` that can easily mean an order of magnitude more

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 Please consider changing this sentence/phrase:

 On repositories like clients that can easily mean an order of magnitude more

We need a count of people or teams blocked per break on clients to back "an order of magnitude," or perhaps we drop the magnitude and say the cost multiplies by the number of blocked people. 🤔

### Monorepo

Fold the whole SDK into the `clients` repository, the way `jslib` was folded in before. A change to
the SDK lands in the same commit as the consumer updates it requires, and main is never broken,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 Please consider changing this sentence/phrase:

the SDK lands in the same commit as the consumer updates it requires, and main is never broken,

to

the SDK lands in the same commit as the consumer updates it requires, and main keeps building,

as versioned packages that other consumers, such as the server, depend on; only the in-repo clients
drop the version boundary. The cost is a large one-time migration and a bigger repository.

## Decision outcome

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ We have been working lately to be more concise with the decision as simply the choice.

Please read through this and consider what's restated that can be removed; or needs to be moved to the plan; or belongs in the Jira/Confluence document that is the actual work. If that Jira/Confluence section doesn't exist, then one suggestion is to add that to the PR description as a follow-up things that will happen if the ADR is accepted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see the last 4-5 ADRs that have been merged and accepted (starting with maybe the diagraming one) for potential ideas about what the direction has been as of late with this section in particular.

- Look into automatically generated mobile fixes together with the mobile team. These will not be a
merge requirement: requiring a working or merged mobile PR before an SDK change can merge would
bring back the cross-repo blocker this decision removes.
- Work closer with the mobile team, and address problems at the boundary as we run into them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Who is working closer with the mobile team?

send access, which is application logic, so like the client apps it is a consumer of the product,
not a shared crate. Its feasibility is unaffected by this decision.

The upfront migration is real work, but it is one-time work, and we have done a version of it before

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️/🎨 This paragraph reads as a repeat of several places; mostly in the positive and negative consequences. Let's consider removing it.

@coroiu

coroiu commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

@quexten Thank you for you feedback! I think it is obvious that you critical to the approach that the ADR currently recommends (and it's great that you are sharing that), but I'd be curious to also get your feedback on what you would prefer instead. Have you looked at the other alternatives in the ADR? Do any of them match your preferences, or is there an alternative that I've missed?

@quexten

quexten commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

@coroiu So, to solve this issue my first question is: What are the specific types of breaking changes we are seeing? Why was it necessary to perform a breaking change in the first place? (Given you cite statistics most likely gotten from a claude eval over the codebase, it should be easy to have those auto-categorized).

Are these just a temporary issue due to us moving things into the SDK, or is this a persistent pattern?

For example, when considering moving vault item crypto to the SDK, yes that is breaking, but it is only breaking now where the migration traffic is high. That won't be an issue a few months from now.

So, as for my preference:

  • Ban breaking changes (additive-only): never change the exported surface in place; add alongside, deprecate, reap.

this is close, but I don't think simply banning is effective. You have to make it easy for people to succeed. Evidently, people just don't know how to write APIs right now that are stable. How do we help them? By giving them 1. examples of how to design an API that doesn't break / is stable in contrib docs / on existing clients 2. adding that as part of the claude code review. This should be based on the patterns that we have observed to be breaking.

  • Versioned SDK releases: keep the repos separate, but release the SDK as its own deliverable
    every two weeks (semver, a changelog of breaking changes) instead of publishing on every merge to
    main, and let consumers upgrade when it suits them.

This is close too, and closer to my preference.

As part of adding an API we should always ask: "Who are the possible consumers, how will they consume it, and is it likely that this will change in the future?" If the answer is "this will change" that needs to be either planned for, or we need to re-think the API.

Then, we should have way fewer breaking changes added.

This would help both external consumers and mobile.

I will say that mobile at least already uses a bunch of API surface that is not stable, so we will have breaking changes moving them off of it. The same applies to clients.


### Plan

- Fold `sdk-internal` into the `clients` repository, following the `jslib` precedent, so the SDK and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying out of DM; this is missing a few steps:

  • How are skills handled?
  • How are claude reviews handled? Do we have one reviewer per sub project somehow?
    • The context is too much and too different to fit across the whole repo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely a good point to look into!

When it comes to the context, we could have different reviewers for each language package, but having one reviewer for the entire thing also has it's benefits, like giving the agent the opportunity to review the entire feature from start to finish

@quexten

quexten commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Having a stable API for crypto can also be seen as desirable.

@coroiu it very much is. I will note that most of the used API surface however, is considered deprecated (even if not yet marked as such). We are working on exposing only the stable crypto APIs, but as so often it's a prioritization thing...

@quexten quexten left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make my objection clear on the grounds that this change in my opinion (and other KM members opinion) makes the situation actively directly worse, by encouraging breaking changes and making them easier, I'm changing my review from "comment" to "Request changes".

My recommendation of stabilizing changes plus @mzieniukbw recommendation of making people prepare PR's here: https://bitwarden.slack.com/archives/C04LE162PHA/p1791803147344229?thread_ts=1791793761.811589&cid=C04LE162PHA apply.

- **Versioned SDK releases:** keep the repos separate, but release the SDK as its own deliverable
every two weeks (semver, a changelog of breaking changes) instead of publishing on every merge to
main, and let consumers upgrade when it suits them.
- **Product repository:** split the SDK crates. Move the application-logic crates into a product

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much all of these options are much lighter and reversible compared to the monorepo, which is irreversible. Should we find out that, as claimed in other comments, the monorepo does not have the intended effect, how do we revert that descision? Can we first try other, lighter options, and measuring their success?

A 12-month measurement (2025-09-01 to 2026-08-31, read from merged PRs and git history) shows the
shape of the problem:

- **68 forced downstream breaking events**: clients 18, android 28, iOS 22. The earlier six months

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying from slack:

  1. What is the nature of these breaking changes?
    I.e trivial or hard to resolve?
  2. Were they instantly resolved?
  3. How long was development actually stalled by these?

Next, most of these. are on mobile, with clients just being a minority share. 18 out of 68. This ADR only addresses clients.

- One-time migration cost, with a precedent (`jslib`), against a coordination cost that otherwise
grows with the number of contributing teams.

### Negative consequences

@audreyality audreyality Oct 12, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 A few additional negative consequences:

  • Rust's build output is very large (multiple gigabytes) and people with many working copies will have a copy of all build artifacts in every repository.
  • Rust has long build times. People with a worktree-based workflows (including claude code run with --worktree <desc>) will experience on every worktree.
  • This encourages people to make breaking changes to the client interfaces by lowering its cost. We may see the rate of breaking changes to client interfaces increase as a result (The Jevons paradox).

Comment on lines +257 to +258
- **The server's dependency on the SDK.** `server` currently depends on `sdk-internal` for
cryptographic and policy code. Folding the SDK into `clients` should not turn that into a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the SDK has been added to server yet. If it is, it's not used for policy code. (I did mention this as a future possibility, but no more than that.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used for seeding.

@mzieniukbw mzieniukbw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The objective is questionable. The solution proposed does not address the problem. It degrades our productivity. In fact, the problem itself is questionable, since it is not a measure of the impact on the teams.
We have not tried enough and have not improved our current processes much, to be able to say, that, what we have is not working and have to look for the alternative, like the ADR proposes.


We have tried to manage this without changing the structure. The breaking-change detection workflow
builds the latest `clients` `main` against SDK PRs so a breaking change can arrive with its fix PR
ready. It has not been enough: breaking changes have gone up, not down, even with the workflow in

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking changes is bad metric. Rather it should be, whether the team got impacted by the breaking change. In all clients, SDK update is a separate PR that does not immediately break main. AI's auto-fix only made it easier to handle.
Unless i am missing the point here, i don't feel like "breaking changes" should be treated as a root cause problem that we need to fix.

A 12-month measurement (2025-09-01 to 2026-08-31, read from merged PRs and git history) shows the
shape of the problem:

- **68 forced downstream breaking events**: clients 18, android 28, iOS 22. The earlier six months

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a problem with lack of any enforcements, rather than a problem that requires solving.

within 14 days; 143 of 149 within 3 days. A consumer picking up a new version does not adopt one
break, it adopts a bundle of unrelated breaks at once.
- **Nothing gates the publish.** npm and Maven publish on every merge to main. The breaking-change
check runs on PRs, comments, and adds a `breaking-change` label, but never fails the job (a

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I often found that the clients itself did not build due to issue not related to SDK change and added breaking-change label either way.
Have we filtered those out ?

Comment on lines +48 to +51
branch, with median branch windows of about 80 and 165 hours, but that window is dominated by
adaptation effort and the branch sitting open between re-bumps, not by waiting for a reviewer, and
per-break approval time cannot be cleanly separated from it. The cost across all three repos is
coordination and adaptation, not code review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but that window is dominated by adaptation effort and the branch sitting open between re-bumps

The cost across all three repos is coordination and adaptation, not code review.

With AI auto-fix on mobile and AI driven development, the adaptation and code review efforts merged together.
I think we should re-consider this ADR entirely, since the amount of adaptation effort with AI auto-fix likely reduced from hours to minutes (adopt to all clients), which feels like the main driver of the ADR (effort / impact on teams).

Comment on lines +55 to +57
- **The churn is in the application layer.** Breaks across the dataset are dominated by SDK-owned
surface changes (struct and field reshapes, renames, unions, new APIs), and the clients coupling
points are mostly vault, tools, admin-console, and importer code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once mobile clients fully adopt the SDK on higher level designed SDK APIs, rather than low level Uniffi split structs and functions (mostly due to mTLS issue), the amount of breaking changes would reduce significantly.
Since this is upcoming effort planned by Platform team for this year, i would recommend to take an iterative approach: migrate to higher level SDK APIs in mobile clients and re-evaluate again in mid 2027.
We might find that 80% of a breaking changes for mobile were hidden behind this limitation.

Comment on lines +157 to +158
between them to keep in sync. A change to the SDK and the client code that uses it becomes one
change that only merges if it builds. The cross-repo breaking change stops being a separate thing to

@mzieniukbw mzieniukbw Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A change to the SDK and the client code that uses it becomes one change that only merges if it builds.

This negatively impacts the PR, since both SDK and TS changes might have to bundled together. This would severely increase PR sizes and time required for a team to adopt them due to increased amount of dependent teams.
Teams might get discouraged by this (like KM team), and effectively will split the SDK and TS code, so neither breaks each other.
This is effectively the same as banning breaking changes in practise.

Comment on lines +149 to +150
as versioned packages that other consumers, such as the server, depend on; only the in-repo clients
drop the version boundary. The cost is a large one-time migration and a bigger repository.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only the in-repo clients drop the version boundary

⚠️ The biggest cost is compilation time and as a result dev's productivity. Should be mentioned in negative consequences.
Rust is known to be relatively slow to compile, hence additional 10-15 minutes compilation time every single time SDK changes is huge cost. In practice, that's every day. This will result in significant wait time to run lint, run tests or run client. All depends on he WASM build, which is not light.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

adr ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

9 participants