Builds checking out stale code on merge commits

I’m encountering strange behaviour in my master builds, the cloned code appears to be an older than the expected commit. I confirmed this by ssh’ing into the job and listing files, new files added in the latest commit were not present. This seemed bizarre, so I had a look at the .git folder in the cloned repository and saw that HEAD was indeed referencing the latest commit hash in master, yet the files do not match.

This seemed like an issue with the checkout step, however the real kicker is that I only encounter this behaviour once code is merged into master (and thus CI is triggered on the merge commit). With this same code in a branch/pull request, the step correctly checks out the current code as expected (again, confirmed by ssh’ing in).

My first thought was in regards to caching during builds, but the only path I am caching is my node_modules directory for dependencies, everything else is straight from the checkout: step.

steps:
  - run:
      name: Run latest update on base image
      command: |
        apk --no-cache update && \
        apk add ca-certificates
  - checkout
  - attach_workspace:
      at: ~/circleci-deployment
  - restore_cache:
      key: database-dependencies-cache-{{ checksum "./database/package.json" }}-{{ checksum "./database/package-lock.json" }}
  - run:
      name: Install NPM module dependencies for database
      command: |
        cd ./database
        npm install
  - save_cache:
      paths:
        - ./database/node_modules
      key: database-dependencies-cache-{{ checksum "./database/package.json" }}-{{ checksum "./database/package-lock.json" }}
  - run:
      name: DB Migration
      command: |
        cd ./database
        npm run db:migration:apply

If I ssh into that last db migration step in master, the files are not up to date (missing database migration files). If I do it on a PR build with the same steps, the files are correctly checked out and run.

Any advice or pointers would be greatly appreciated, thanks.