Web-Claude opens PRs from stale branches; runs git as root in user trees (silent corruption path)

Resolved 💬 3 comments Opened May 3, 2026 by Nursedude Closed May 7, 2026

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

  1. Web-Claude is asked to fix a small bug (e.g. CI test failure).
  2. It branches off whatever its local notion of main is.
  3. That local notion is stale — sometimes by tens of commits — because

web-Claude has no automatic git fetch + behind-count check before
committing.

  1. 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.

  1. 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.sh propagates 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

  1. Operator owns a clone at /opt/<repo>/, working tree owned by <user>:<user>.
  2. Web-Claude (or a tool web-Claude invokes) runs sudo git pull or

sudo git fetch against that tree at some point.

  1. Git, running as root, writes new refs to .git/refs/remotes/origin/*

and pack files to .git/objects/pack/* as root-owned.

  1. 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.

  1. 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/main was

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 unprivileged
git 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. Bare
sudo git ... against a user-owned tree is the antipattern that
created this bug.

(For context: the operator caught this antipattern in their own
fleet_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 fetch returns 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

  1. Operator opens local Claude Code session (workstation, full repo access).
  2. Operator independently invokes web-Claude on the same set of repos

via claude.ai.

  1. Both surfaces are asked to "ship prevention tooling" or otherwise

work on similar themes.

  1. 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 shipping
claude/prevention-tooling-2026-05-03 branches to both
RNS-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

  1. 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.

  1. 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.

  1. 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/meshforge PR #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/meshforge commits c6bfab5, 1b92679, 1ad40df,

28c15a3, b785af3, 95d36b2

  • Nursedude/meshanchor commits 7eed5cdd, 76ace4e0, 5ba2dcdd
  • Nursedude/RNS-Meshtastic-Gateway-Tool commit cd2748a

The operator is happy to provide repo access or further detail on
request.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗