---
title: "The Easy Guide to Resolving composer.lock Conflicts"
url: "/articles/easy-guide-resolving-composerlock-conflicts"
type: article
date: 2024-07-17
updated: 2024-10-03
---

# The Easy Guide to Resolving composer.lock Conflicts

#  The Easy Guide to Resolving composer.lock Conflicts 

 How to resolve composer.lock file conflicts, why all of this work is necessary, and steps you can take to minimize these conflicts in the first place.

 By

 [  Andrew Berry   ](/about/andrew-berry) 

 July 17, 2024

  

[Resolving merge conflicts](https://getcomposer.org/doc/articles/resolving-merge-conflicts.md) is a great *advanced* part of Composerâ&#128;&#153;s documentation, but weâ&#128;&#153;ve found that it leaves many readers more confused than confident. If there is a composer.lock conflict, all you get is this error message:

```bash
$ git pull origin main
Auto-merging composer.lock
CONFLICT (content): Merge conflict in composer.lock
Recorded preimage for 'composer.lock'
Automatic merge failed; fix conflicts and then commit the result.
```

Thanks, git! But how do you fix these conflicts?

First, you must know what Composer commands were run on your own branch. In most cases, itâ&#128;&#153;s running `<span>composer require</span>` or `<span>composer update</span>` to get a Drupal module. For this example, weâ&#128;&#153;re going to assume itâ&#128;&#153;s `<span>drupal/environment_indicator</span>`. Weâ&#128;&#153;ll also assume youâ&#128;&#153;re using `<span>ddev</span>` for your local environment. If not, drop `<span>ddev</span>` from the copy-paste block below.

```bash
# Stop the merge and roll back any changes - we'll retry later.
git reset --hard
# Update your main to match the remote main.
git checkout main && git pull
# Install dependencies so when you run commands later it's clear what is changing.
ddev composer install
# Checkout whatever branch you were on first.
git checkout -
# Restart the merge.
git merge main
# Get the composer.lock file from main and check it out.
git checkout --theirs -- composer.lock
# Re-run your composer commands.
ddev composer require drupal/environment_indicator
git add composer.json composer.lock
git commit
```

Now, push your code and hope you can get it in before you need to repeat the process (or â&#128;&#156;rerollâ&#128;&#157;) again!

## OK, But Why All This Work?

A `composer.lock` file contains a `content-hash` property, which is where every conflict originates. Why couldnâ&#128;&#153;t we just remove the property entirely and be done with conflicts forever?

[The Composer docs](https://getcomposer.org/doc/articles/resolving-merge-conflicts.md) provide an in-depth explanation of why we need to prevent text-based merges. Perhaps we can also find an original issue describing why a [hash](https://en.wikipedia.org/wiki/Hash_function) (and not some other mechanism) was chosen to prevent text-based merges of lock files.

The first code adding a lockfile hash is over at [Locking mechanism implemented #40](https://github.com/composer/composer/pull/40) in the Composer repository. There are no notes describing why adding a hash was needed. The closest is [Implement lock mechanism #28](https://Implement%20lock%20mechanism%20#28) which unfortunately doesnâ&#128;&#153;t answer this.

But the truth is, if youâ&#128;&#153;ve used JavaScript package managers like `npm` or `yarn`, youâ&#128;&#153;re probably asking yourself, â&#128;&#156;Why canâ&#128;&#153;t we just do in Composer what they do?â&#128;&#157;.

It boils down to a key difference in the PHP and JavaScript languages. JavaScript can use closures to scope dependencies to just their consuming parent. In other words, this means that in a single project, Library A can depend on `Library B 1.0`, and `Library C` can depend on `Library B 2.0`, and JavaScript is totally fine with that.

PHPâ&#128;&#153;s class loader simply doesnâ&#128;&#153;t work that way. When a PHP class is loaded, that is the one version of that class for the lifecycle of the PHP request. You couldnâ&#128;&#153;t have version 1 and version 2 of a PHP library (or Drupal module) at the same time unless they renamed the library or class paths.

Given that constraint, Composer cannot implement nested dependencies like you can in the JavaScript ecosystem. Thatâ&#128;&#153;s probably a good thingâ&#128;&#148;nested dependencies are a major pain point in the JavaScript ecosystem and the cause of many security vulnerabilities in the software supply chain.

Knowing that Composer has to implement a *flat* dependency tree (where there is only ever one version of a dependency at a time), we can see how the lock file can represent this - as a simple array of package objects in the `packages` key:

```json
{
    "_readme": [
        "This file locks the dependencies of your project to a known state",
        "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
        "This file is @generated automatically"
    ],
    "content-hash": "4c03575acd7a47b4a22ffd1947c31b91",
    "packages": [
        {
            "name": "composer/ca-bundle",
            "version": "1.5.0",
...
```

The `<span>content-hash</span>` property serves as a flag describing all of the state of the Composer packages that could affect the rest of the file. We can see what is currently used to create this hash in [the Locker class](https://github.com/composer/composer/blob/902a153741e04043a40d94362973d073309f768d/src/Composer/Package/Locker.php#L76-L111). Of note are the relevant keys - keys in `<span>composer.json</span>` not in this list wonâ&#128;&#153;t change the lock hash!

```php
$relevantKeys = [
   'name',
   'version',
   'require',
   'require-dev',
   'conflict',
   'replace',
   'provide',
   'minimum-stability',
   'prefer-stable',
   'repositories',
   'extra',
];
```

One way to summarize the effect of the `<span>content-hash</span>` property is that it makes the `<span>composer.lock</span>` file act like an *opaque binary file*. While we can read the JSON, when it comes to git merges, we should pretend itâ&#128;&#153;s not human-readable. Instead, we should recreate the steps that will give us the merged file we want, as if we were merging two different JPEG files.

## Strategies to Reduce Conflicts

We use a few techniques to avoid Composer lock file conflicts in the first place. Youâ&#128;&#153;ll never avoid them (especially early in a project when youâ&#128;&#153;re adding modules every day), but you can reduce the number of times you need to update a merge request.

### Create a Pull Request For Composer Changes Only

In many tickets, there are several steps - require a module, install it, configure it, and write end-to-end tests for the new functionality. These donâ&#128;&#153;t all need to be in a single pull request! For Composer dependencies, it can save a lot of time if you file a PR that just runs `<span>composer require â&#128;¦</span>` with no other changes. After all, the module isnâ&#128;&#153;t enabled or in use yet, so there isnâ&#128;&#153;t much code review to do. Push it up and get it in!

### Use Renovate for Upgrades

We use [Renovate](https://docs.renovatebot.com) on all of our projects to [manage dependency upgrades](https://www.lullabot.com/articles/managing-software-updates-hundreds-websites). Renovate can rebase and reroll Composer package upgrades automatically whenever `<span>main</span>` is changed. Developers only need to run Composer commands to add or remove modules or for major Drupal upgrades. And because we can quickly get dependency upgrades in, we arenâ&#128;&#153;t constantly rerolling the lock file.

### Add composer validate to CI Checks and Build Scripts

Itâ&#128;&#153;s the worst when you go to update a Composer dependency and discover that whatâ&#128;&#153;s on `<span>main</span>` is already broken. You can avoid this by running `<span>composer validate</span>` in pull request checks. A good practice is to add it before `<span>composer install</span>` in your automated test jobs. Itâ&#128;&#153;s very quick to run and will catch if youâ&#128;&#153;ve forgotten to commit changes in either `<span>composer.json</span>` or `<span>composer.lock</span>`.

## A Future Without Conflicts?

Weâ&#128;&#153;ve considered treating Composer changes more similarly to how other systems treat database migrations. In this model, developers would commit the *steps* to transform a Composer lock file (with require, update, and remove commands), allowing systems to automatically reroll the lock file. We do this in pull request descriptions for work like upgrading major Drupal versions, so why not automate it? But in practice, weâ&#128;&#153;ve found that with the strategies above, committing these steps just hasnâ&#128;&#153;t been worth the effort to implement.

Thereâ&#128;&#153;s both [RFC: Automatically resolve conflicts in composer.lock #6929](https://github.com/composer/composer/issues/6929) and [\[feature\] Automatically recover from trivial merge conflicts in composer.lock#11517](https://github.com/composer/composer/pull/11517) with years of discussion between the two of them. However, as of today, thereâ&#128;&#153;s no better way to resolve conflicts than by following the steps in this article.

 Published in:

- [  Drupal Development   ](/topics/drupal-development)
- [  System Administration   ](/topics/system-administration)

 

   You must have JavaScript enabled to use this form.

## Get in touch with us

### Tell us about your project. We'd love to hear from you!

 First Name  

 Last Name  

 Your email address  

 Your organization  

 How you heard about us  

 Tell us more 

     Leave this field blank