From b1b109c8c2099bd148c60b3c9a2231ea79d0745a Mon Sep 17 00:00:00 2001 From: Anas Khan <83116240+anxkhn@users.noreply.github.com> Date: Sun, 28 Jun 2026 23:31:33 +0530 Subject: [PATCH] fix(js-sdk): don't treat "detached" in a branch name as a detached HEAD parseGitStatus flagged detached: true whenever the porcelain branch line contained the substring "detached", via `rawBranch.includes('detached')`. A normal status line like `## main...origin/detached-work` (or a local branch such as `feature/detached-fix`) was therefore misclassified as a detached HEAD, and currentBranch/upstream were dropped. Real detached HEADs print `## HEAD (no branch)` or `## HEAD (detached at )`, which are already handled by the `startsWith('HEAD (detached at ')` clause and the `normalizedBranch.startsWith('HEAD')` branch, so the substring check only ever produced false positives. Drop it. This is the JS twin of the Python SDK fix for the same defect (#1373). --- .../js-git-status-detached-substring.md | 5 +++ packages/js-sdk/src/sandbox/git/utils.ts | 4 +-- packages/js-sdk/tests/git.parseStatus.test.ts | 31 +++++++++++++++++++ 3 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 .changeset/js-git-status-detached-substring.md create mode 100644 packages/js-sdk/tests/git.parseStatus.test.ts diff --git a/.changeset/js-git-status-detached-substring.md b/.changeset/js-git-status-detached-substring.md new file mode 100644 index 0000000000..08fa8c8ed3 --- /dev/null +++ b/.changeset/js-git-status-detached-substring.md @@ -0,0 +1,5 @@ +--- +"e2b": patch +--- + +Fix `parseGitStatus` so a branch or upstream whose name merely contains the substring `detached` (for example `main` tracking `origin/detached-work`) is no longer misreported as a detached HEAD. Detached-HEAD detection now keys off the `HEAD (detached at )` / `HEAD (no branch)` porcelain forms only, so `currentBranch` and `upstream` are preserved. This mirrors the Python SDK fix for the same defect (#1373). diff --git a/packages/js-sdk/src/sandbox/git/utils.ts b/packages/js-sdk/src/sandbox/git/utils.ts index 6df7fbc888..fc3ae59dc8 100644 --- a/packages/js-sdk/src/sandbox/git/utils.ts +++ b/packages/js-sdk/src/sandbox/git/utils.ts @@ -381,9 +381,7 @@ export function parseGitStatus(output: string): GitStatus { aheadStart === -1 ? undefined : branchInfo.slice(aheadStart + 2, -1) const normalizedBranch = normalizeBranchName(branchPart) const rawBranch = branchPart - const isDetached = - rawBranch.startsWith('HEAD (detached at ') || - rawBranch.includes('detached') + const isDetached = rawBranch.startsWith('HEAD (detached at ') if (isDetached || normalizedBranch.startsWith('HEAD')) { detached = true diff --git a/packages/js-sdk/tests/git.parseStatus.test.ts b/packages/js-sdk/tests/git.parseStatus.test.ts new file mode 100644 index 0000000000..306ef9bf4c --- /dev/null +++ b/packages/js-sdk/tests/git.parseStatus.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from 'vitest' + +import { parseGitStatus } from '../src/sandbox/git/utils' + +// Twin of the Python fix in PR #1374 (issue #1373): `parseGitStatus` used to flag +// `detached: true` for any branch/upstream name merely containing the substring +// "detached", dropping `currentBranch`/`upstream` for ordinary branches. + +test('upstream whose name contains "detached" is not a detached HEAD', () => { + // Branch 'main' tracking 'origin/detached-work': NOT a detached HEAD. + const status = parseGitStatus('## main...origin/detached-work\n') + expect(status.detached).toBe(false) + expect(status.currentBranch).toBe('main') + expect(status.upstream).toBe('origin/detached-work') +}) + +test('local branch whose name contains "detached" is not a detached HEAD', () => { + const status = parseGitStatus('## feature/detached-session-fix\n') + expect(status.detached).toBe(false) + expect(status.currentBranch).toBe('feature/detached-session-fix') +}) + +test('real detached HEAD ("## HEAD (no branch)") is still detected', () => { + const status = parseGitStatus('## HEAD (no branch)\n') + expect(status.detached).toBe(true) +}) + +test('real detached HEAD ("## HEAD (detached at )") is still detected', () => { + const status = parseGitStatus('## HEAD (detached at 1a2b3c4)\n') + expect(status.detached).toBe(true) +})