Skip to content

app-server-broker silently disables all git hooks when started in a git worktree #697

Description

@CDD-Jarvis

Summary

Starting the Codex app-server broker with --cwd pointing at a linked git worktree writes two values into the repository's shared .git/config within ~25 seconds:

core.bare      = true
core.hooksPath = .no-hooks

.no-hooks does not exist. Git therefore silently skips every hook for the whole repository — not just for the Codex session, and not just in that worktree. core.bare = true additionally makes the main checkout and every linked worktree unusable (fatal: this operation must be run in a work tree).

Nothing warns the user. A push that skips all pre-push checks looks exactly like a normal push.

Environment

Plugin codex 1.0.6 (marketplace openai-codex)
Codex CLI codex-cli 0.147.0
git 2.55.0
node v24.19.0
OS macOS 26.6.2, arm64

The repository uses extensions.worktreeConfig = true and husky (core.hooksPath = .husky/_).

Reproduction

# in any git repo that has at least one commit
REPO=$(pwd)

git config core.bare            # -> false
git config core.hooksPath       # -> e.g. .husky/_

git worktree add -b probe /tmp/probe-wt HEAD

node <plugin>/scripts/app-server-broker.mjs serve \
  --endpoint "unix:$(mktemp -d)/b.sock" \
  --cwd /tmp/probe-wt \
  --pid-file "$(mktemp -d)/b.pid" &

sleep 30

git config core.bare            # -> true      (was false)
git config core.hooksPath       # -> .no-hooks (was .husky/_)

Expected

The broker should not persist configuration into the user's shared repository
config. If it needs to suppress hooks for its own git invocations, that belongs
in the invocation scope, for example git -c core.hooksPath= … or the
GIT_CONFIG_COUNT / GIT_CONFIG_KEY_* environment, not in .git/config.

Actual

Both values are written persistently to the shared config and are never
restored
, including after the session ends.

Key discriminator

--cwd points at Result
a linked worktree corrupted after ~25 s (reproduced repeatedly)
the main checkout clean after 45 s of observation

So the trigger is specific to linked worktrees. A plausible mechanism: in a
linked worktree .git is a file pointing at .git/worktrees/<name>. Something
in the startup path appears to mis-detect that as a bare repository and then
persists both core.bare and the hook suppression into the common config
shared by every worktree.

Impact

This is a security issue, not only an annoyance:

  • All local hooks are disabled repository-wide, for every worktree and the
    main checkout, not just for the Codex session.
  • Every locally enforced pre-push check is removed at once, whatever a given
    project runs there.
  • The failure is invisible. Git does not warn about a core.hooksPath
    that points nowhere, so pushes simply proceed with no local check having
    run and look completely normal. In our case the condition was only noticed
    because unrelated git commands started failing due to core.bare = true;
    without that side effect it could have gone unnoticed indefinitely.
  • Anyone who runs Codex sessions in worktrees is affected, and the more sessions
    are started, the more often it recurs. We measured multiple occurrences within
    a single hour.

Workaround

A watcher that restores both values. We run a small script on a launchd
WatchPaths trigger against .git/config; it repairs about one second after
each corruption. Note that putting such a repair inside a git hook does not
work: with a broken core.hooksPath no hook runs, so it cannot fire in the one
situation it is needed for.

Notes

core.bare was occasionally observed flipping back to false on its own, while
core.hooksPath stayed broken. So the two writes may come from different code
paths, and the hook suppression is the more persistent and more dangerous of the
two.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions