Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for GitLab CI/CD By James Joyner IV · · 8 min read

GitLab CI Error Guide: 'detected dubious ownership' — Fix Git safe.directory

Quick answer

Fix 'detected dubious ownership in repository' in GitLab CI: add the build path to git safe.directory, align UID ownership, and unblock git on runners.

  • #gitlab
  • #ci-cd
  • #troubleshooting
  • #errors
Free toolkit

Fixing errors like this? Get 500 free DevOps AI prompts

500 copy-paste AI prompts for the stack you actually run — one PDF, free.

Overview

Git refuses to operate on a repository whose working directory is owned by a different user than the one running git. In GitLab CI this surfaces the moment a job runs any git command against the build directory:

fatal: detected dubious ownership in repository at '/builds/team/app'
To add an exception for this directory, call:

	git config --global --add safe.directory /builds/team/app

The job’s git step (describe, submodule, tag, diff, or even the runner’s own checkout) fails, and any script that shells out to git stops here. This is a Git security feature (CVE-2022-24765), not a GitLab bug.

Symptoms

  • A git command fails with detected dubious ownership in repository at '<path>'.
  • The failure started after a base-image upgrade to a newer Git version.
  • The job runs git as a different user (e.g., root in the container) than the one that owns /builds/... (or vice versa).
  • Tooling that internally calls git — git describe, semantic-release, setuptools_scm, submodule init — fails even though your explicit script looks fine.
  • The same repo works on an older runner image but not a rebuilt one.

Common Root Causes

  • UID mismatch between checkout and job — the runner (or a previous job) created /builds/... as one UID; the job image runs git as another.
  • Container running as a non-root user while the build dir is owned by root, or the reverse.
  • Reused build directory across images — a cached/persistent /builds volume owned by a different UID than the current image’s user.
  • Newer Git enforcing the check — Git 2.35.2+ hardened ownership checks; an image upgrade turned a previously-silent mismatch into a fatal.
  • Docker volume/bind mount ownership — a mounted host path owned by a UID that doesn’t match the container user.
  • Rootless/DinD user namespacing — remapped UIDs make the checkout appear owned by an unexpected user inside the container.

Diagnostic Workflow

Confirm the ownership mismatch by comparing who owns the dir to who runs git:

ownership-debug:
  script:
    - id                              # UID/GID running the job
    - ls -ld "$CI_PROJECT_DIR"        # owner of the build directory
    - git status || true              # reproduces the dubious-ownership fatal

The direct fix is to mark the build path as safe for git in the job:

build:
  before_script:
    - git config --global --add safe.directory "$CI_PROJECT_DIR"   # exact path
  script:
    - git describe --tags

To cover reused/generic runner paths, allow all directories (acceptable on ephemeral CI where the runner controls the environment):

build:
  before_script:
    - git config --global --add safe.directory '*'   # trust all — CI-only convenience
  script:
    - ./release.sh

Alternatively, align ownership so the check passes without exceptions:

fix-ownership:
  before_script:
    - chown -R "$(id -u):$(id -g)" "$CI_PROJECT_DIR"   # make git's user own the repo
  script:
    - git status

Example Root Cause Analysis

A release job that generated a version string with git describe began failing on every pipeline right after the team bumped their build image from Debian 11 to Debian 12:

release:
  image: node:20-bookworm      # newer image => newer Git with hardened ownership check
  script:
    - VERSION=$(git describe --tags)   # <-- fatal: detected dubious ownership
    - npm run release -- --version "$VERSION"

The runner checked out the repo into /builds/team/app as one UID (the runner’s helper), but the node:20-bookworm image ran the script as a different user. The older Debian 11 Git had not enforced the ownership check; Debian 12’s newer Git did, turning the long-standing UID mismatch into a fatal error. The fix added the build path to safe.directory before any git use:

release:
  image: node:20-bookworm
  before_script:
    - git config --global --add safe.directory "$CI_PROJECT_DIR"
  script:
    - VERSION=$(git describe --tags)
    - npm run release -- --version "$VERSION"

Because the failure was really a UID mismatch surfaced by a Git upgrade, the team also standardized the runner’s FF_.../user settings so checkout and job ran as the same UID, and added the safe.directory line to their shared CI template so future images inherit the fix.

Prevention Best Practices

  • Add git config --global --add safe.directory "$CI_PROJECT_DIR" to a shared before_script/template so every job that touches git inherits it.
  • On ephemeral CI runners you control, safe.directory '*' is an acceptable blanket fix; avoid it on shared multi-tenant hosts where trust boundaries matter.
  • Keep the checkout UID and the job image’s user aligned; chown the build dir to the job user when they must differ.
  • Expect ownership fatals after Git version bumps (2.35.2+) and add the safe.directory config proactively when upgrading base images.
  • For persistent/reused /builds volumes, ensure the volume UID matches the images that use it.
  • Bake the safe.directory setting into custom CI images (via a global gitconfig) so downstream jobs don’t each have to set it.

Quick Command Reference

# Who runs git vs. who owns the repo?
id
ls -ld "$CI_PROJECT_DIR"

# Direct fix — trust the exact build path
git config --global --add safe.directory "$CI_PROJECT_DIR"

# CI-only blanket trust (ephemeral runners you control)
git config --global --add safe.directory '*'

# Align ownership instead of adding an exception
chown -R "$(id -u):$(id -g)" "$CI_PROJECT_DIR"

# Bake into a custom image's global gitconfig
git config --system --add safe.directory '*'

Conclusion

detected dubious ownership in repository is Git’s security check firing because the build directory is owned by a different user than the one running git — usually a UID mismatch between the runner’s checkout and the job image, often surfaced by a Git version bump. Add the build path to git safe.directory (or align ownership with chown), put it in a shared CI template so every job inherits it, and standardize checkout/job UIDs so the mismatch doesn’t recur after your next base-image upgrade.

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.