') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); The worktree recipe in CLAUDE.md / AGENTS.md still writes upstream config, which can fail after creating the branch — the objectui half of the objectstack --no-track hardening · Issue #6880 · objectstack-ai/objectui · GitHub
Skip to content

The worktree recipe in CLAUDE.md / AGENTS.md still writes upstream config, which can fail after creating the branch — the objectui half of the objectstack --no-track hardening #6880

Description

@zhuangjianguo

Cross-repo drift created by a change landing next door. Filing rather than widening that PR.

What is drifting

objectstack has hardened its prescribed worktree recipe to git worktree add --no-track ... (root AGENTS.md Prime Directive #11 and root CLAUDE.md), because the default -b form writes the new branch's upstream keys — branch.NAME.remote and branch.NAME.merge — into the one .git/config that every linked worktree of a repo shares. That write can fail after the branch has been created, leaving a branch with no worktree: a half state in the very first step of the recipe, and one the error text does not mention.

objectui still prescribes the unhardened form, in two places:

  • CLAUDE.md:16git fetch origin main && git worktree add ../REPO-TASK -b BRANCH origin/main && cd ../REPO-TASK && pnpm install
  • AGENTS.md:234 — the same recipe inline, spelled git worktree add ../objectui-TASK -b BRANCH origin/main

(Placeholders written as words here; the files use angle-bracket placeholders.)

Why it matters here specifically

The hazard is not about which repo the text lives in — it is about what an agent does with the error. Read as "the whole command failed", the retry hits a different error (branch already exists) that reads as a second, unrelated problem. Read as "the worktree exists", the agent starts editing the shared primary checkout — which is the single thing the worktree-first rule exists to prevent, and objectui's own hook and AGENTS.md §9 discipline are built on that rule holding.

objectui is also the repo where the shared-namespace family has bitten before: #3430 (shared refs/stash), #5700 (shared refs/remotes/*), #6208 (the recipe branching off an unfetched local main). This is the same family, one namespace further out: config, not refs.

Measured, on git 2.43.0

Reproduced in an isolated scratch repo (bare origin plus a clone, nothing shared):

recipebranch.NAME.remote afterbranch.NAME.merge afterworktree HEAD
worktree add PATH -b BRANCH origin/mainoriginrefs/heads/main= origin/main
worktree add --no-track PATH -b BRANCH origin/mainABSENTABSENT= origin/main

--no-track removes the config write and still bases the worktree on origin/main. Explicit git pull origin main is unaffected (it uses the given refspec, never branch.NAME.merge), and git push -u origin BRANCH — already the prescribed first push — sets an upstream one command later.

What is inference, not measurement

That the failing config write lost a lock race against a sibling worktree writing config in the same instant is strongly indicated but was not reproduced deliberately, in either repo. The half state itself is directly measured. The remedy does not depend on the cause being right: .git/config is one shared file that -b writes, whatever made a particular write fail.

Suggested shape

Insert --no-track into both recipe spellings above, and — if objectui's own line budget allows it, which objectstack's did not — name .git/config alongside refs/stash and refs/remotes/* in the shared-namespace text.

Worth checking whether cloud carries the same recipe; not verified from here.

Not fixed in the objectstack PR — out of scope for that card. Companion to objectstack#13052.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions