Web-Claude opens PRs from stale branches; runs git as root in user trees (silent corruption path)
Web-Claude — three composing bugs with silent-corruption / data-loss potential
Filed by: an operator running a 5-Pi mesh-network NOC (~50k LoC across
6 first-party Python repos) under a hybrid workflow:
- Local Claude Code sessions on a workstation with full repo access
- Web-Claude (claude.ai with GitHub PR integration) opening PRs to the
same repos in parallel
The two surfaces don't see each other, and both can write. Tonight's
session exposed three bugs in the web-Claude side that compose into a
silent-corruption / data-loss path. Caught by the operator before damage
landed; would not have been caught by review heuristics that look at
title + small-diff confidence.
---
Issue A — Web-Claude opens PRs from stale branches with no staleness signal
Severity: HIGH (silent data-loss path on merge)
Reproduction
- Web-Claude is asked to fix a small bug (e.g. CI test failure).
- It branches off whatever its local notion of
mainis. - That local notion is stale — sometimes by tens of commits — because
web-Claude has no automatic git fetch + behind-count check before
committing.
- Web-Claude makes a 2-line fix, commits, opens a PR. The PR's diff
against the current base shows the 2-line intended change PLUS a
wholesale revert of every commit between the stale base and the
current tip.
- The PR title and description describe only the 2-line intended fix.
The diff stat tells the truth, but only if reviewed.
Concrete instance
- Repo:
Nursedude/meshforge - PR: #1153 (closed without merge after detection)
- Title: ordinary "fix CI" framing
- Branch: 54 commits behind
main - Diff stat: would have reverted approximately 10,875 lines of work
(the MeshChatX integration, federation, node_history Issues #49/50/52,
multiple security-hardening commits, the gateway prevention pass)
- Caught by: operator manually inspecting the diff stat before merge
Expected behavior
Any of:
- Web-Claude refuses to open a PR from a branch >N commits behind base
without an explicit --allow-stale override
- Web-Claude rebases automatically before pushing (preferred), failing
loudly on conflict
- Web-Claude injects a banner into the PR body when behind threshold,
e.g. > :warning: This branch is 54 commits behind main and includes a
wholesale revert of those commits. Rebase before merging.
Actual behavior
PR was opened with no warning. The only signal was the line-count delta,
which is invisible in the GitHub PR notification email and in a casual
title-skim.
Impact if undetected
If merged on title-and-small-diff reflex (the normal review pattern for
routine fixes):
- 10,875 lines reverted in one commit on
main. - Operator's
fleet_sync.shpropagates the revert to all 5 production
Pis within 24h via fast-forward git pull --ff-only.
- Production regresses to 2-3 weeks earlier code: federation breaks,
recent gateway fixes vanish, several known-and-fixed Issues recur.
- Diagnosis is hard because the symptom is "production suddenly broke"
without an obvious correlated change in monitoring.
- Recovery requires force-push (a destructive operation the user
generally avoids), revert reapplication, and per-box re-sync.
Operator-side mitigation shipped tonight
scripts/check_pr_overdue.sh— manual safety check before merging any
PR (commit c6bfab5)
.github/workflows/pr_overdue_check.yml— auto-comments on PRs >20
commits behind, labels overdue, deletes the comment on rebase
(commit 95d36b2 in meshforge, 5ba2dcdd mirror in meshanchor)
These workarounds are operator-side and only cover repos the operator
controls. The product-side fix should be upstream, in web-Claude
itself, before the PR ever reaches the operator's repo.
Suggested fix
Pre-PR-open, web-Claude should run:
git fetch origin <base-ref>
behind=$(git rev-list --count HEAD..origin/<base-ref>)
if [ "$behind" -gt 20 ]; then
# rebase or warn loudly
fi
A 1-line GitHub API call (compare) gives the same answer without a
local fetch.
---
Issue B — Web-Claude runs git as root in user-owned trees, creating silent ref-update corruption
Severity: HIGH (silent corruption — no error, no log, just wrong state for weeks)
Reproduction
- Operator owns a clone at
/opt/<repo>/, working tree owned by<user>:<user>. - Web-Claude (or a tool web-Claude invokes) runs
sudo git pullor
sudo git fetch against that tree at some point.
- Git, running as root, writes new refs to
.git/refs/remotes/origin/*
and pack files to .git/objects/pack/* as root-owned.
- After web-Claude's session ends, the operator's subsequent
unprivileged git fetch cannot update those root-owned ref files.
git fetch exits 0 (successful) but the ref doesn't actually move.
No warning, no error, no log entry.
- Every subsequent operation (PR-open, branch-list, "what's the latest
on main") sees a stale origin/main. The longer this goes on, the
more divergent the state.
Concrete instances
Two repos exhibited this state when tonight's session inspected them:
/opt/RNS-Management-Tool/.git/—refs/remotes/origin/mainwas
root-owned, last updated 2026-04-16. Currently-correct origin/main
was 17 days ahead. Multiple claude/* branches (web-Claude's
branches) were also root-owned, all stuck at their April creation
timestamps.
/opt/RNS-Meshtastic-Gateway-Tool/.git/— same shape, same root
ownership on refs and pack files.
After sudo chown -R <user>:<user> /opt/<repo>/, the next unprivilegedgit fetch finally moved the ref:
From https://github.com/Nursedude/RNS-Management-Tool
1bc28a7..dc1b109 main -> origin/main
* [new branch] claude/prevention-tooling-2026-05-03 -> origin/...
That 1bc28a7..dc1b109 is 17 days of refs that had been silently
unable to update.
Expected behavior
Web-Claude must never run git operations as root against trees it
doesn't own. If elevation is needed for path access (e.g. the working
tree is in a system location), the correct invocation is:
sudo -u <owner> git ...
— which preserves the owning UID for any files git writes. Baresudo git ... against a user-owned tree is the antipattern that
created this bug.
(For context: the operator caught this antipattern in their ownfleet_sync.sh tonight as part of the same audit — commit 1ad40df
in meshforge. The same rule applies on the web-Claude side.)
Actual behavior
Web-Claude's tooling appears to elevate to root for some git operations
without dropping privilege via sudo -u. Files end up root-owned. The
operator notices nothing because git silently no-ops the failed ref
updates instead of erroring.
Impact if undetected
This is the scarier of the three issues because it has no surface
signal. The symptoms manifest as:
- PRs opened from increasingly stale bases (this is the root cause of
Issue A — web-Claude's "main" stops being main)
- Operator's local
git fetchreturns success but doesn't actually
fetch
- Tooling that depends on
origin/<branch>accuracy
(CI status checks, PR-overdue checks, branch-protection rules)
silently misreports
- The longer this goes on, the more divergent each tool's view of the
repo becomes
Suggested fix
Audit web-Claude's git-invocation paths for any sudo git or
root-context git execution. Replace with sudo -u <owner> git where
elevation is genuinely needed. Add a runtime guard: refuse to run git
operations when geteuid() == 0 and the target tree is not root-owned.
---
Issue C — No cross-session coordination between web-Claude and other Claude surfaces
Severity: MEDIUM (known architectural limitation; mitigation possible)
Reproduction
- Operator opens local Claude Code session (workstation, full repo access).
- Operator independently invokes web-Claude on the same set of repos
via claude.ai.
- Both surfaces are asked to "ship prevention tooling" or otherwise
work on similar themes.
- Both produce work that conflicts at merge time. Neither is aware of
the other.
Concrete instance
Tonight, while local Claude Code was shipping c6bfab5, 1b92679,28c15a3, and b785af3 to Nursedude/meshforge (prevention tooling +
fleet runtime health + test fixes), web-Claude was shippingclaude/prevention-tooling-2026-05-03 branches to bothRNS-Management-Tool and RNS-Meshtastic-Gateway-Tool — independent
prevention-tooling work, same date-stamp, no cross-reference.
Why this is harder to fix than A or B
Web-Claude is fundamentally a sandboxed remote agent. It can't see the
operator's local working trees, in-flight commits, or other concurrent
sessions. That's a property of the architecture, not a bug.
Suggested mitigation
Either:
- Web-Claude should pre-flight against the GitHub API for branches
matching claude/<topic>-<date> and warn if a similar effort is
already in flight (low cost, partial coverage)
- OR document this clearly to operators ("only run one Claude per repo
at a time") and rely on the Issue A staleness check to catch the
divergence at merge time (operator-side mitigation)
This issue is not catastrophic on its own — it produces conflict at
merge time, which is normal-developer-noise rather than silent-data-loss.
Filed for completeness, not as a blocker.
---
How the three issues compose
Issue B causes Issue A. Issue A enables silent data loss.
B: web-Claude runs `sudo git pull` against user-owned tree
→ root-owned refs in .git/
→ silent fetch failures
→ web-Claude's "origin/main" goes stale
→ web-Claude opens PR off stale base (Issue A)
→ 54-commits-behind PR with routine title
→ operator merges on title+small-diff reflex
→ 10,875-line revert lands
→ fleet_sync propagates
→ production regression
Each link in this chain is silent. The operator has to be paranoid at
review time to break it. Tonight the operator was paranoid. Most days
they wouldn't be — review-by-title is the standard heuristic for small
fixes.
Counterfactual without operator catching it
Best-case: PR #1153 merged. Operator notices in 1-3 days when something
breaks in production. ~4 hours of forensics + force-push + reapply.
Worst-case: PR #1153 merged AND the silent-fetch corruption (Issue B)
keeps producing more PR-#1153-shaped artifacts. Operator spends a week
chasing "why is production drift happening" before identifying the
root-cause class. Trust in web-Claude collapses.
What we'd ask Anthropic to do
- Issue A — fix urgency: high. Add a pre-PR-open staleness check.
Even just a banner in the PR body would let the diff-stat warning
travel with the PR through email and GitHub UI. The full fix is
auto-rebase or refuse-loud.
- Issue B — fix urgency: high. Audit web-Claude's git-invocation
tooling for any path that runs git as root in user-owned trees.
Replace bare sudo git with sudo -u <owner> git. This bug has
been brewing in at least one operator's environment for 2+ weeks
silently.
- Issue C — fix urgency: low. Document the no-cross-session-
visibility constraint clearly. Optionally pre-flight against
GitHub API for matching claude/*-<date> branches.
Repos / commits cited
For Anthropic's reproduction or telemetry lookups:
- meshforge:
Nursedude/meshforgePR #1153 (closed) - RNS-Management-Tool:
Nursedude/RNS-Management-Tool—
silent-fetch demonstration, before/after chown
- RNS-Meshtastic-Gateway-Tool:
Nursedude/RNS-Meshtastic-Gateway-Tool—
same
- Operator-side mitigations shipped tonight:
Nursedude/meshforgecommitsc6bfab5,1b92679,1ad40df,
28c15a3, b785af3, 95d36b2
Nursedude/meshanchorcommits7eed5cdd,76ace4e0,5ba2dcddNursedude/RNS-Meshtastic-Gateway-Toolcommitcd2748a
The operator is happy to provide repo access or further detail on
request.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗