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.
Summary
Starting the Codex app-server broker with
--cwdpointing at a linked git worktree writes two values into the repository's shared.git/configwithin ~25 seconds:.no-hooksdoes 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 = trueadditionally 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
codex1.0.6 (marketplaceopenai-codex)codex-cli0.147.0The repository uses
extensions.worktreeConfig = trueand husky (core.hooksPath = .husky/_).Reproduction
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 theGIT_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
--cwdpoints atSo the trigger is specific to linked worktrees. A plausible mechanism: in a
linked worktree
.gitis a file pointing at.git/worktrees/<name>. Somethingin the startup path appears to mis-detect that as a bare repository and then
persists both
core.bareand the hook suppression into the common configshared by every worktree.
Impact
This is a security issue, not only an annoyance:
main checkout, not just for the Codex session.
project runs there.
core.hooksPaththat 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
gitcommands started failing due tocore.bare = true;without that side effect it could have gone unnoticed indefinitely.
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
WatchPathstrigger against.git/config; it repairs about one second aftereach corruption. Note that putting such a repair inside a git hook does not
work: with a broken
core.hooksPathno hook runs, so it cannot fire in the onesituation it is needed for.
Notes
core.barewas occasionally observed flipping back tofalseon its own, whilecore.hooksPathstayed broken. So the two writes may come from different codepaths, and the hook suppression is the more persistent and more dangerous of the
two.