Fix Git cannot lock ref and empty object file errors
Separate a busy Git writer, a stale lock, a conflicting branch name and damaged object storage before attempting a repair. Work through concrete evidence, preserve unpublished work, and verify a separate replacement checkout without deleting the original repository.
Restore a trustworthy Git workflow after cannot lock ref or an object file is empty error, while preserving the working tree, local commits and evidence needed to explain the failure. The outcome may be a safely resumed checkout or a verified replacement; a missing object with no surviving copy cannot be reconstructed by a generic command.
- Git 2.x
- Shell POSIX-compatible shell on Linux or macOS
- An identified repository and owner Know the exact checkout that failed, whether it is shared with another user or job, and who can pause its writers. Obtain permission to inspect repository metadata and any alternate object store. The commands below are for a trusted repository, not for executing an unknown downloaded project.
- A recoverable copy before repairs Use your approved backup or snapshot procedure after pausing writers. Include tracked, untracked and ignored working files, the index, refs, reflogs, Git metadata and external metadata paths. A new clone or a Git bundle alone is not a backup of everything currently present. Verify the copy can be opened independently and record its location.
- Storage health and controlled access Have enough free space for a separate checkout on healthy storage. If logs show I/O errors, stop repeated scans and arrange storage-level preservation first. Do not put access tokens into a clone URL, terminal transcript, issue comment or example variable.
OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.
Full guide
What you will build
- A cause-specific incident record separating lock coordination, ref naming and object damage.
- A verified preserved source and, where needed, an independent replacement checkout with explicit recovery limits.
- Explain why the exact Git error occurred instead of applying a generic .git deletion recipe.
- Demonstrate integrity, required history and local-file preservation before resuming normal Git operations.
Architecture
How the parts fit together
Git coordinates reference updates over an object store while the working tree and index carry additional local state. Recovery must respect those separate responsibilities.
- Capture the exact operation, error suffix and metadata paths.
- Pause writers and preserve the complete local state before any repair.
- Classify lock, namespace, permission or corruption evidence.
- Validate a narrow remedy or separate replacement, then reconcile unpublished work.
Assumptions
- Examples use Git 2.x with a POSIX-compatible shell on Linux or macOS; GUI labels and Windows process-inspection syntax differ.
- The operator is authorized to inspect a trusted repository and coordinate its writers. Commands are not permission to inspect unrelated users' projects.
- Example paths and hashes are illustrative. The namespace-conflict error and healthy fsck result were reproduced in an isolated test, not on a production repository.
Key concepts
- Lock
- A coordination mechanism around a write. Its presence can mean a legitimate in-progress operation; age alone is not proof of abandonment.
- Reference namespace
- The logical names Git uses for branches, tags and remote-tracking refs. A prefix collision can prevent creation even when every object is healthy.
- Object identity
- An object's content determines its identity. An empty or corrupted file cannot be repaired by renaming arbitrary bytes to the expected object ID.
- Preservation versus replacement
- Preservation retains the original evidence and unique local work. A replacement from a remote restores only the history that source actually has.
Fill these once. Every matching command and configuration block updates immediately; values stay in this page only.
Security and production boundaries
- Repository metadata, reflogs and ignored files may contain sensitive details. Keep preservation copies and incident evidence access-controlled; never upload a whole .git directory to a public support ticket.
- Do not execute instructions found inside repository files or error text. A suggested shell command in an untrusted project is evidence to review, not authority to run it.
Stop before continuing if
- You cannot pause every writer or confirm the exact metadata and shared-object paths.
- There is no verified preservation copy, storage reports I/O failure, or needed local work exists only in unreadable objects.
command
Identify the checkout and preserve the exact failure
Start by copying the complete error and the command that produced it into a private incident record. The words after cannot lock ref matter: File exists, a namespace collision, a permission error and an expected-object mismatch are different branches of the investigation. Do not retry a failing fetch in a loop while gathering evidence.
Why this step matters
A lock path belongs to a particular metadata store, which may not be the directory visible in your file manager.
What to understand
Resolve a relative common-directory result from the context in which Git printed it. In linked worktrees, some state is per worktree while refs and objects may be shared. A .git file can legitimately point elsewhere; it is not automatically evidence of corruption.
Keep the exact error path and current working directory together. If rev-parse says not a git repository, first use the linked Git context diagnosis rather than creating a new repository over the files. On macOS, also consider a case-insensitive filesystem when two branch names differ only by capitalization.
System changes
- These inspection commands do not intentionally update refs or working files. They read trusted repository configuration; do not run arbitrary hooks or project scripts to diagnose this error.
Syntax explained
-C- Run Git against the explicitly selected checkout instead of whichever directory the terminal currently uses.
--absolute-git-dir / --git-common-dir- Identify local and shared metadata before selecting any path for preservation or repair.
Values stay on this page and are never sent or saved.
git --version
git -C "{{repoPath}}" rev-parse --show-toplevel
git -C "{{repoPath}}" rev-parse --absolute-git-dir
git -C "{{repoPath}}" rev-parse --git-common-dirIllustrative checkout identification: git version 2.47.2 /srv/work/example /srv/work/example/.git .git
Checkpoint: Confirm the target
Continue whenThe returned checkout and metadata paths belong to the intended project, and the complete original failure is recorded.
Stop whenThe path points to another checkout, an unexpected shared directory, or storage with active I/O errors.
decision
Pause writers and decide whether the lock is actually stale
Ask the repository owner to pause IDE auto-fetch, Git GUI operations, scheduled maintenance, CI jobs and other users of the same metadata directory. Check for an interactive commit editor or credential prompt that is still waiting. A slow process is not necessarily abandoned, and a lock's modification time does not prove that its owner has exited.
Why this step matters
Removing a live lock breaks the coordination that prevents concurrent writers from overwriting each other's reference updates.
What to understand
Use the host's process viewer and, where available, an approved open-file inspection tool for the exact lock path. Lack of visible processes is not conclusive when another container, user, machine or network-filesystem client can write the same store. Coordinate at the shared repository owner boundary.
Record what has been paused, the affected checkout and who may resume it. Do not terminate unrelated Git processes merely because their executable name matches. If the active operation finishes and the lock disappears normally, retry only the original bounded operation and keep the incident evidence.
Before a repair, make and verify the complete preservation copy described in the prerequisites. If the original has become unreadable, a storage specialist may need to capture an image. Repeated Git commands on a failing disk can consume the remaining time available to retrieve data.
System changes
- No automated process termination or lock deletion is performed. Pausing a job or client is an explicit operational action coordinated with its owner.
Illustrative incident evidence: operation: fetch origin error suffix: Unable to create .../refs/remotes/origin/main.lock: File exists IDE auto-fetch: paused by owner CI checkout job: still running Decision: wait for the known writer; do not remove its lock.
Checkpoint: Choose the lock branch
Continue whenEither a live writer is identified and allowed to finish, or all possible writers are quiescent and a verified preservation copy exists.
Stop whenA writer cannot be identified or paused, the repository is shared beyond your visibility, or there is no recoverable copy.
command
Inspect reference names without editing their storage files
For a namespace error, inspect the logical reference names through Git. The reproducible example below was tested in an isolated empty-commit repository: creating topic succeeds, while trying topic/sub reports cannot lock ref. The object integrity check still succeeds because a name conflict is not object corruption.
Why this step matters
Git's reference API sees loose, packed and supported alternative reference storage consistently; filesystem guesses can miss an existing name.
What to understand
A reference named topic conflicts with a proposed descendant topic/sub. Determine whether the conflicting name is a local branch or a remote-tracking ref before choosing a remedy. Compare the exact spelling and capitalization, not just the shortened label displayed by an IDE.
The safe default is to choose a non-conflicting new name or coordinate a deliberate rename with the branch owner. Do not delete a remote branch or force-push to make a local checkout work. If a remote-tracking name is obsolete, first inspect the approved remote and its advertised refs before considering a reviewed prune.
For an error saying a ref is at one object but another was expected, preserve both object IDs. That may be Git correctly rejecting a race with another writer. Stop the competing operation and reassess; removing a lock does not fix an expectation mismatch. Do not hand-edit packed-refs or reftable files.
System changes
- Lists reference names and object IDs only. The failure shown above was generated in a disposable test repository, not in the selected checkout.
Syntax explained
for-each-ref --format- Print logical ref names and their object IDs without interpreting output as shell code.
refs/heads refs/remotes- Limit the inspection to local branches and remote-tracking refs; inspect additional namespaces only if the original error names them.
Values stay on this page and are never sent or saved.
git -C "{{repoPath}}" for-each-ref --format="%(refname) %(objectname)" refs/heads refs/remotesReproduced namespace-conflict error (creation attempt is not part of the inspection command): fatal: cannot lock ref 'refs/heads/topic/sub': 'refs/heads/topic' exists; cannot create 'refs/heads/topic/sub' The inspection shows refs/heads/topic. The failed branch creation exits 128; git fsck --full exits 0 in the isolated fixture.
command
Check object integrity and separate missing data from harmless leftovers
Run the integrity scan only after preservation and only on storage healthy enough for a potentially expensive read. An object file is empty, object is corrupt, missing blob or hash mismatch is a data-integrity signal. A dangling object alone is different: it can be surviving history from a reset or rebase and may be useful for recovery.
Why this step matters
A full object check helps distinguish a reference coordination failure from missing repository data before any state-changing remedy is considered.
What to understand
Keep the scan's exit status and complete diagnostics. Packed objects and configured alternate object stores are included in a full check; the relevant damage may be outside the visible .git/objects directory. In partial or shallow clones, interpret missing-history expectations with someone familiar with that clone mode.
Do not add --lost-found to a read-only evidence step: that option writes recovered entries. Do not run gc, prune, reflog expire or a cleanup script while trying to preserve reachable or unreachable history. Such maintenance is not a reconstruction algorithm.
If the first read reports hardware I/O errors, stop and return to storage preservation. A successful scan says something about the objects it examined, not that your untracked files, deployment configuration or application data have been backed up. Record those checks separately.
System changes
- Reads repository objects and references; it can cause substantial disk activity but does not intentionally repair objects or rewrite refs.
Syntax explained
--full- Check objects in the repository, packs and configured alternate object pools rather than assuming all content is in loose files.
Values stay on this page and are never sent or saved.
git -C "{{repoPath}}" fsck --fullIllustrative corruption report, not the healthy namespace fixture: error: object file .git/objects/ab/cdef0123456789abcdef0123456789abcdef012345 is empty fatal: loose object abcdef0123456789abcdef0123456789abcdef012345 is corrupt Healthy fixture: exit status 0, no corruption diagnostics.
Checkpoint: Classify repository integrity
Continue whenThe evidence distinguishes a healthy object store with a lock/name issue from actual corruption, and identifies every reported missing or corrupt object.
Stop whenThere are storage I/O errors, the scan is too costly for the host, or the result cannot be interpreted for the repository's clone mode.
decision
Apply only the remedy supported by the evidence
For a live writer, let it finish and resume normal use. For a confirmed stale lock, an authorized operator may quarantine exactly the named lock after all writers are stopped and the preservation copy is verified. Do not publish a broad deletion command: the correct path can be outside the working tree and differs between reference backends.
Why this step matters
A narrow, evidence-backed action keeps the failure reversible and avoids treating every lock or metadata file as disposable.
What to understand
Record the original path, ownership, timestamps and quarantine destination. Use a move operation that does not overwrite an existing destination. Do not restore a quarantined lock over a lock created by a resumed Git operation. If the retry fails, preserve the new evidence and return to diagnosis rather than repeating deletion.
A permission failure needs ownership, ACL, mount and security-policy diagnosis. Do not recursively chmod a shared repository or use sudo git as a universal workaround. That can create a second ownership problem and bypass the intended access boundary.
A namespace conflict is resolved at the branch naming level. Use a reviewed non-conflicting name when possible. Renaming an established branch affects upstream tracking, jobs and collaborators; document those dependencies before an authorized change. Never treat local success as permission to mutate the remote.
An empty object file does not belong in the stale-lock branch. Removing the corrupt file may change the next error without restoring its contents. Preserve the original and move to an independent replacement or a supervised recovery plan.
System changes
- Any approved remedy affects only its documented lock or branch configuration. There is no automated deletion, recursive permission change, force-push or reset in this guide.
Illustrative reviewed decision: classification: abandoned lock after interrupted local fetch scope: one exact lock path from the recorded error preconditions: all writers stopped, snapshot verified, filesystem healthy remedy: move that lock to restricted quarantine; retry the single fetch once stop: any new lock conflict or changed ownership.
Alternatives
- Keep the checkout quarantined and create a separate verified checkout while a repository specialist investigates.
Stop conditions
- Do not proceed if writers, target paths, ownership or the preservation copy are uncertain.
command
Create an independent replacement when a trusted remote has the needed history
A separate clone can restore published history when the approved remote is healthy. It cannot restore unpublished commits or untracked files that only existed in the damaged checkout. Use a new destination on healthy storage and leave the original untouched. Prefer a network remote; do not clone the damaged local directory and accidentally borrow its broken objects.
Why this step matters
Separating the replacement from the damaged source avoids overwriting the only surviving copy of local work during validation.
What to understand
Verify the remote host and project identity through your existing trusted channel. Use a credential helper or approved SSH configuration rather than embedding credentials in the URL. Confirm the destination does not contain another project. A clone may invoke configured transport and credential helpers, so this is for a trusted source.
--no-checkout obtains the repository before materializing its working files. Inspect the fetched refs and integrity first. Do not run build scripts, package installation or repository-supplied executables merely to decide whether its Git objects are sound.
If you use a local backup as the source, account for Git's local-copy optimizations and alternate storage. A recovery operator should explicitly choose an independent copy method; this command is intentionally documented for an approved network URL, not an arbitrary filesystem path.
Compare the required branch tips with the incident record. A healthy replacement that lacks a local feature branch is not a complete recovery. Ask collaborators or inspect verified backups for the missing object chain before declaring that work lost or discarding the original.
System changes
- Creates a new repository at the approved replacement path and transfers data from the approved remote. It does not overwrite or repair the original checkout.
Syntax explained
clone --no-checkout- Create a separate repository and fetch remote history without populating the working tree yet.
remoteUrl / replacementPath- An approved HTTPS or SSH repository URL and a new, separate absolute directory on healthy storage.
Values stay on this page and are never sent or saved.
git clone --no-checkout "{{remoteUrl}}" "{{replacementPath}}"Illustrative network-clone progress: Cloning into '/srv/work/example-verified'... Receiving objects: 100% (...), done. Resolving deltas: 100% (...), done. No working-tree checkout is requested yet.
command
Inventory surviving local history before switching work to the replacement
On the preserved original or its verified recovery copy, inspect local reflogs if the objects remain readable. A reflog is a local record, not a remote backup. Copying its text into another repository does not restore the commits or blobs referenced by it. Record candidate object IDs and ask the recovery owner to validate their complete object chains.
Why this step matters
Unpublished commits and untracked files can be the only unique data in the damaged checkout, so replacing it without an inventory risks permanent loss.
What to understand
Recover readable local changes through an approved export/import procedure against the separate verified repository. Keep an explicit checklist for staged changes, unstaged changes, untracked files and ignored-but-important configuration. Do not assume a patch covers binary files or ignored secrets unless the chosen procedure explicitly includes them.
If objects are unreadable, retain the image or copy and look for a known-good copy in backups or another authorized collaborator's clone. Do not fabricate a new object under the missing hash or download an object from an untrusted source. If no copy exists, state the recovery limit clearly.
Treat reflog subjects and filenames as potentially sensitive. Redact customer names and credentials from shared reports. The original remains restricted until the owner confirms every required branch and working file is accounted for, or formally records which data could not be recovered.
System changes
- Reads local reflog records only. No reflog expiration, branch reset, checkout or object rewrite is requested.
Syntax explained
reflog show --all- Inspect retained local ref-change records across refs; this is not a fetch of remote history.
--date=iso- Include interpretable timestamps for comparison with the incident and backup windows.
Values stay on this page and are never sent or saved.
git -C "{{repoPath}}" reflog show --all --date=iso --format="%H %gd %gs"Illustrative surviving local history:
0123456789abcdef0123456789abcdef01234567 HEAD@{2026-09-08 12:15:00 +0000} commit: local configuration change
This identifies a candidate commit; it does not prove all of its tree and blob objects are readable.verification
Verify the replacement and resume one controlled operation
Validate the selected repository before making it the new working location. Compare branch names and object IDs to the preservation record, confirm that required local work was transferred, and perform one approved operation that originally failed. A successful fetch alone is not evidence that all local changes survived.
Why this step matters
Separate integrity, history completeness and workflow checks prevent a superficially successful clone from being mistaken for a complete recovery.
What to understand
After the owner accepts the repository, check out the intended branch through the normal reviewed workflow and restore only the approved local files. With --no-checkout, an unpopulated working tree is expected; do not misread that state as another corruption incident.
Resume paused clients one at a time. If the lock conflict returns only when two jobs share a checkout, give them independent working directories or serialize their writes. Keep the original preservation copy for the agreed retention period rather than deleting it immediately after the first green command.
System changes
- The commands shown only verify the replacement. Resuming jobs and checking out a branch are separate approved operational actions.
Syntax explained
fsck / for-each-ref / rev-parse- Check object integrity, the logical ref inventory and the actual metadata location as separate acceptance criteria.
Values stay on this page and are never sent or saved.
git -C "{{replacementPath}}" fsck --full
git -C "{{replacementPath}}" for-each-ref --format="%(refname) %(objectname)" refs/heads refs/remotes
git -C "{{replacementPath}}" rev-parse --absolute-git-dirIllustrative acceptance record: fsck: exit 0, no missing/corrupt object diagnostics required remote refs: matched approved source local-only work: independently accounted for original checkout: retained in restricted quarantine single approved retry: succeeded
Checkpoint: Accept or keep investigating
Continue whenIntegrity is sound, all required history and local work are accounted for, and the original operation succeeds without recurring lock conflicts.
Stop whenAny object is missing, local-only work is unaccounted for, the remote identity is uncertain, or the failure recurs.
Finish line
Verification checklist
git -C "{{replacementPath}}" fsck --fullExit status 0 with no missing or corrupt object diagnostics, interpreted in the context of the clone mode. Dangling objects alone do not authorize deletion.git -C "{{replacementPath}}" for-each-ref --format="%(refname) %(objectname)" refs/heads refs/remotesRequired ref names and object IDs are matched to the incident record and approved remote. Local-only history and non-Git files are checked separately.Recovery guidance
Common problems and safe checks
The lock reappears immediately after quarantine.
Likely causeA still-active writer, auto-fetch client or scheduled job is recreating it; the lock was not the underlying problem.
Identify the exact path and creation time of the new lock.Correlate the event with the approved IDE and CI schedules without killing unrelated processes.
ResolutionStop repeated lock removal, coordinate writers, and give concurrent jobs independent checkouts or explicit serialization.
The replacement clone works but a feature branch is missing.
Likely causeThe branch was local-only, never pushed to the approved remote, or was absent from the selected source.
Compare the preserved ref inventory and reflog with the replacement.Ask the authorized branch owner whether a backup or collaborator has the required commits.
ResolutionRecover from a verified intact source; do not discard the original or report a complete recovery until the gap is reconciled.
fsck reports dangling commits but no corruption.
Likely causeHistory editing or interrupted operations left readable objects that are not current branch tips.
Preserve the objects and inspect relevant reflog entries.Distinguish dangling from missing, corrupt or hash-mismatch diagnostics.
ResolutionDo not run cleanup as a repair. Evaluate whether the surviving objects contain needed local work.
Reference
Frequently asked questions
Does cannot lock ref mean my repository is corrupt?
No. A healthy repository can report cannot lock ref when another writer holds a lock or when a proposed branch conflicts with an existing name. The tested topic versus topic/sub fixture produces that error while fsck succeeds. Read the complete suffix and inspect the relevant paths and refs before choosing a remedy.
Can I delete every .lock file under .git?
No. Some may belong to active writers, and linked worktrees can share metadata outside that directory. After all writers are stopped and a complete preservation copy is verified, an authorized operator may quarantine the single confirmed stale lock named in the failure. Blanket deletion is not a safe troubleshooting step.
How do I fix an empty object file?
Preserve the original, check storage health, identify the affected objects and obtain intact copies from a verified source. A separate clone can restore published history but not unpublished work absent from that remote. Deleting the empty file alone does not recreate the object. If no intact copy survives, recovery may be incomplete.
Will git reset --hard or git gc repair missing objects?
They are not general object-reconstruction tools. Reset can discard working changes, and cleanup can remove surviving unreachable history needed for recovery. Keep them out of the initial incident workflow. Validate an intact source and preserve the damaged checkout before a specialist selects a targeted recovery action.
Is a .git file instead of a directory an error?
Not necessarily. Worktrees and submodules can use a .git file that points to another metadata directory. Use rev-parse to identify the real Git and common directories. Do not replace that file with git init or delete it because a file-manager view looks unfamiliar.
Recovery
Rollback
The primary recovery boundary is the untouched original plus its verified preservation copy. A new clone is additive, not an in-place replacement. No blanket reset, object deletion or remote rewrite is part of this procedure.
- If a proposed replacement fails validation, stop using it and keep it separate; do not copy its metadata over the original. Preserve new error evidence and return to the recorded cause branch.
- If a narrow lock quarantine did not resolve the issue, do not repeatedly remove new locks or blindly restore an old one. Stop writers again and compare state under the recovery owner's control.
- If work was switched to a replacement, revert the working-directory or job configuration only after checking for new commits and files created there. Preserve both sides until the owner has reconciled them.
Evidence