5 min read
On this page

Decision Making

Every open source project makes hundreds of decisions: which features to build, which APIs to design, which dependencies to adopt, which PRs to merge. Most of these decisions are small and uncontroversial. A handful are large, contentious, and project-defining. The way a project handles decisions determines whether it moves forward efficiently or gets stuck in endless debate. The best projects have explicit decision-making processes that match the weight of each decision: lightweight for small choices, structured for big ones.

Lazy Consensus

Lazy consensus is the default decision-making mechanism for most open source projects. The principle is simple: silence means agreement. If a proposal is made and nobody objects within a reasonable timeframe, it is accepted.

How lazy consensus works:

  1. Someone proposes a change
     "I'd like to add TypeScript support. Here's my plan."

  2. A waiting period begins
     Typically 72 hours for minor decisions, 1-2 weeks for larger ones.

  3. Community members can:
     - Agree explicitly ("+1", "sounds good")
     - Object with reasons ("I disagree because...")
     - Stay silent (counts as agreement)

  4. If no objections: the proposal passes
     The proposer proceeds with implementation.

  5. If objections arise: discussion continues
     The proposer addresses concerns, modifies the proposal,
     or escalates to a formal vote.

Why Lazy Consensus Works

Benefits:
  - Fast: most decisions pass without needing active input from everyone
  - Low overhead: no voting infrastructure, no quorum requirements
  - Respects people's time: you only need to engage if you disagree
  - Scales well: works for both small and large communities

Requirements for lazy consensus to work:
  - Proposals must be visible to all stakeholders (mailing list,
    GitHub issue, discussion)
  - The waiting period must be long enough for people in different
    time zones to see it
  - Silence must genuinely mean agreement, not "I didn't see it"
  - People must feel safe objecting

The Apache Software Foundation has used lazy consensus as its primary decision mechanism since 1999. Their rule: if a proposal is posted to the mailing list and receives no negative votes within 72 hours, it passes. This has enabled thousands of decisions across hundreds of projects with minimal process overhead.

RFCs for Big Decisions

When a decision is too significant for lazy consensus, an RFC (Request for Comments) provides a structured process for proposing, discussing, and deciding.

What warrants an RFC:

  - Breaking changes to the public API
  - New major features that affect the project's direction
  - Changes to the governance model
  - Adoption or removal of major dependencies
  - Changes to the release process or versioning strategy
  - Anything that will be hard to reverse

What does NOT warrant an RFC:

  - Bug fixes
  - Documentation improvements
  - Minor feature additions that fit existing patterns
  - Internal refactoring that does not affect the public API
  - Dependency version bumps

The RFC Process

Typical RFC lifecycle:

  1. Pre-RFC discussion
     Informal discussion in an issue or chat. Gauge interest before
     investing time in a full RFC. "I'm thinking about X. Does this
     make sense to pursue?"

  2. Draft RFC
     Author writes a structured document covering the proposal.

  3. Review period
     The RFC is open for community feedback. Typically 2-4 weeks.
     Author responds to feedback and revises the RFC.

  4. Decision
     The governance body (BDFL, committee, or community vote)
     decides to accept, reject, or postpone the RFC.

  5. Implementation
     If accepted, the RFC becomes the spec for implementation.
     The RFC may be updated during implementation if reality
     diverges from the plan.

An RFC document typically includes a summary, motivation, detailed design, drawbacks, alternatives considered, unresolved questions, and prior art. The structure forces thorough thinking before implementation begins.

Real-World RFC Processes

Rust RFCs:
  The Rust RFC process is one of the most rigorous in open source.
  Proposals are submitted as PRs to the rfcs repository. Discussion
  happens in PR comments. The relevant team (lang, compiler, libs)
  makes the final decision. Over 3,000 RFCs have been submitted
  since the process started.

  Notable Rust RFCs:
  - RFC 1105: API evolution guidelines
  - RFC 2094: Non-lexical lifetimes
  - RFC 3498: Lifetime capture rules

Python PEPs:
  Python Enhancement Proposals predate the term "RFC" in open source.
  PEPs cover everything from language changes (PEP 572: walrus
  operator) to process changes (PEP 13: Python governance). The
  steering council makes final decisions on PEPs.

React RFCs:
  React uses an RFC process for significant changes. RFC PRs are
  submitted to the reactjs/rfcs repository. Discussion is open
  to the community, but the React team makes final decisions.

  Notable React RFCs:
  - Hooks (RFC that changed how React components are written)
  - Server Components
  - React Compiler

Voting

When consensus cannot be reached and an RFC discussion stalls, voting provides a definitive resolution. Voting is a last resort in most projects because it creates winners and losers, which can damage community cohesion.

Voting mechanisms:

  Simple majority (>50%):
    Used for most decisions. Whoever gets more votes wins.
    Risk: a 51-49 split means nearly half the community disagrees.

  Supermajority (>66% or >75%):
    Used for significant decisions. Requires broad agreement.
    Ensures that controversial changes have strong support.

  Apache-style voting:
    +1   — binding positive vote (from a committer/PMC member)
    +0   — non-binding positive (support but not strongly)
    -0   — non-binding negative (concern but not blocking)
    -1   — binding veto (must include a technical justification)

    A single -1 with justification can block a proposal.
    This gives minority voices significant power but requires
    them to articulate specific technical concerns.

  Who gets a vote:
    - Only committers/maintainers (Apache model)
    - All contributors above a threshold (Rust model)
    - All community members (democratic model)
    - The governance body only (committee model)

Voting can go wrong in several ways: tyranny of the majority silencing feature teams, voting on subjective matters like design taste producing mediocre compromises, low turnout making results unrepresentative, or coordinated voting used as a weapon to push through controversial changes. Reserve voting for decisions with clear, objective options.

Decision Records

Decisions are worthless if nobody remembers why they were made. Six months later, a new contributor asks "why did we choose X over Y?" and nobody can explain. Decision records solve this by documenting what was decided, when, and why.

Decision record format (based on ADR — Architecture Decision Records):

  # Decision: [Title]

  ## Status
  Accepted / Proposed / Deprecated / Superseded by DR-042

  ## Date
  2026-03-15

  ## Context
  What is the situation? What forces are at play?
  "We need to choose a serialization format for the config file.
  We currently use YAML, but users have reported confusion with
  YAML's implicit type coercion."

  ## Decision
  What was decided?
  "We will migrate from YAML to TOML for configuration files
  starting in v4.0."

  ## Rationale
  Why this choice?
  "TOML has explicit typing, is easier to read, and is widely
  supported. JSON lacks comments. YAML's gotchas (the Norway
  problem, implicit booleans) cause real user bugs."

  ## Consequences
  What are the implications?
  "Users must migrate their config files. We will provide a
  migration tool. YAML support will be deprecated in v4.0 and
  removed in v5.0."

  ## Alternatives Considered
  What else was evaluated?
  "JSON5: supports comments but less tooling. Keeping YAML:
  too many user issues. Custom format: not worth the effort."

Store decision records in a /docs/decisions/ directory or in a dedicated GitHub Discussion category. The format matters less than the habit of recording decisions consistently.

Forks: When Disagreements Cannot Be Resolved

Sometimes a disagreement is so fundamental that no governance process can resolve it. When this happens, the project forks. One codebase becomes two, each pursuing a different vision.

Notable forks in open source history:

  Node.js -> io.js -> merged back (2014-2015):
    Cause: frustration with Joyent's slow release cadence and
    governance under the BDFL model. A group of core contributors
    forked Node.js to create io.js with open governance.
    Resolution: Joyent agreed to transfer Node.js to the Node.js
    Foundation (now OpenJS Foundation). io.js merged back into
    Node.js under the new governance model. The fork succeeded
    by forcing governance reform.

  OpenOffice -> LibreOffice (2010):
    Cause: Oracle acquired Sun Microsystems and the community
    lost confidence in Oracle's stewardship. The Document
    Foundation was created to host LibreOffice.
    Resolution: LibreOffice became the dominant fork. Most Linux
    distributions switched. OpenOffice was eventually donated
    to Apache but has minimal development activity.

  MySQL -> MariaDB (2009):
    Cause: Oracle acquired Sun and MySQL. The original MySQL
    creator, Monty Widenius, forked MySQL to create MariaDB
    as a community-driven alternative.
    Resolution: MariaDB is now the default in most Linux
    distributions. MySQL continues under Oracle with a
    different feature direction.

  Terraform -> OpenTofu (2023):
    Cause: HashiCorp changed Terraform's license from MPL to
    BSL (Business Source License), a non-open-source license.
    The community forked Terraform as OpenTofu under the
    Linux Foundation.
    Resolution: ongoing. Both projects continue development.

Fork when governance has failed to resolve a fundamental disagreement, the license changed unacceptably, the maintainer abandoned the project, or a company captured the project against community interests. Do not fork because you lost a single vote or disagree about a single feature.

Forks are expensive: the community splits, effort is duplicated across similar codebases, users face confusion choosing between them, and the fork needs new branding while the original retains recognition.

The Cost of Indecision

Not deciding is itself a decision. When a project cannot resolve a question, it stalls. Contributors waiting on a decision lose motivation. Users waiting on a feature or fix lose patience. The cost of making a wrong decision and correcting it later is almost always lower than the cost of making no decision at all.

Signs of decision paralysis:

  - RFCs that are open for months with no resolution
  - "We need to discuss this more" repeated indefinitely
  - Competing PRs that both stall because nobody picks one
  - Contributors who stop contributing because they are blocked
    on a decision

The fix:
  - Set deadlines for decisions. "We will decide by April 30."
  - Accept that some decisions can be reversed later.
  - Prefer "good enough now" over "perfect eventually."
  - If consensus cannot be reached in a reasonable time, vote.

Common Pitfalls

  • No documented decision-making process. Without a clear process, every decision becomes a negotiation about how to decide. Document your process in a GOVERNANCE.md file, even if it is simple.

  • Using heavy processes for small decisions. An RFC for renaming a variable wastes everyone's time. Reserve structured processes for decisions that are hard to reverse or affect many people.

  • Lazy consensus without visibility. If a proposal is posted where half the community will not see it, silence does not mean agreement. It means ignorance. Post proposals in visible, canonical locations.

  • Avoiding conflict instead of resolving it. Tabling a contentious issue to avoid uncomfortable discussion does not make the disagreement disappear. It festers. Address conflicts directly with a structured process.

  • Not recording decisions. Six months later, someone reopens a debate that was already resolved because nobody documented the outcome. Decision records prevent this.

  • Forking too easily. A fork should be a last resort, not a first response to disagreement. Forks are expensive for everyone involved. Exhaust all other options first.

Key Takeaways

  • Lazy consensus is the right default for most decisions. Silence means agreement. It is fast, low-overhead, and scales well, as long as proposals are visible to all stakeholders.
  • RFCs provide structure for big decisions. Use them for breaking changes, major features, and anything that is hard to reverse. The Rust and Python RFC processes are models worth studying.
  • Voting is a last resort for when consensus fails. It produces winners and losers, so reserve it for genuinely contentious decisions with clear options.
  • Decision records document what was decided, when, and why. They prevent re-litigating old decisions and help new contributors understand the project's history.
  • Forks happen when governance fails to resolve fundamental disagreements. They are costly but sometimes necessary. The Node.js/io.js fork succeeded by forcing governance reform.
  • Indecision is worse than a wrong decision. Set deadlines, accept reversibility, and prefer progress over perfection.