Summary
On Windows, gstack browse connect has two distinct bugs that together make the sidebar GUI unusable:
- Broken DACL on
.gstack/ state directory — the directory and its child files are created with an ACL containing only the machine SID (S-1-5-21-<machine>, renders as MACHINE\ with empty username and FullControl). Even the directory's owner gets no enumerable access. The next browse invocation can't read its own browse.json, lock files, or browse-startup-error.log. $ browse status reports "server failed to start" while a fully working server is actually listening.
- PTY/terminal-agent sidecar fails to spawn — once the server is reachable,
/health returns {"status":"unhealthy", "chatEnabled":false, "terminalPort":null}. The Side Panel extension then shows "Browse server not ready. Reload sidebar to retry." The chat tab never works.
Environment
- OS: Windows 11 Home Single Language 10.0.26200, x64
- Shell: PowerShell 5.1 + Git Bash
- gstack version: 1.40.0.0 (from
~/.claude/skills/gstack/package.json)
- Binary:
~/.claude/skills/gstack/browse/dist/browse.exe (bun-compiled)
- Server:
~/.claude/skills/gstack/browse/dist/server-node.mjs running under Node 22 from C:\Program Files\nodejs\node.exe
- Chromium: ms-playwright chromium-1208 (Chrome for Testing 145.0.7632.6)
- Invoked from Claude Code v? (Anthropic CLI)
Bug 1: Broken DACL on .gstack/
Repro
- Delete any existing
.gstack/ and ~/.gstack/chromium-profile/
- Run
browse.exe connect from a project directory whose parent has normal ACLs (e.g. user + Administrators + SYSTEM inherited)
- After connect (whether it succeeds or times out at 15s), inspect the newly-created
.gstack/ directory:
> icacls .gstack
.gstack MACHINE\:(OI)(CI)(F)
Successfully processed 1 files; Failed processing 0 files
Note the malformed entry — username is empty, SID is the machine SID with no RID. There is no ACE for the user that ran the command, even though that user is the directory's Owner. The same broken ACL is applied to every file written into .gstack/ (e.g. browse.json, browse-startup-error.log).
> Get-ChildItem .gstack
Get-ChildItem : Access to the path '...\.gstack' is denied.
Same happens for ~/.gstack/chromium-profile/ — the EPERM caught in browse-startup-error.log is:
EPERM: operation not permitted, mkdir 'C:\Users\<user>\.gstack\chromium-profile'
at Object.mkdirSync (node:fs:1377:26)
at BrowserManager.launchHeaded (.../server-node.mjs:8645:9)
at start (.../server-node.mjs:14898:28)
Workaround (confirms the diagnosis)
As Owner, rewriting the DACL using .NET DirectoryInfo.SetAccessControl with AccessControlSections.Access only (avoids needing SeSecurityPrivilege which Set-Acl demands) fixes enumeration immediately:
$di = New-Object System.IO.DirectoryInfo('.gstack')
$sec = New-Object System.Security.AccessControl.DirectorySecurity
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule(
[System.Security.Principal.WindowsIdentity]::GetCurrent().User,
'FullControl',
'ContainerInherit,ObjectInherit', 'None', 'Allow')
$sec.SetAccessRuleProtection(\$false, \$true) # inherit from parent
$sec.AddAccessRule($rule)
$di.SetAccessControl($sec)
This needs to be done on .gstack/ AND ~/.gstack/ AND every file written inside them (the broken ACL is re-applied per-file, not just on the directory).
Likely cause
Whatever code creates these directories/files is passing an explicit security descriptor that contains only the machine SID — looks like mkdir/fs.writeFile is being called with a custom mode/security parameter that maps to a Windows ACL with no user grants. Could be:
- A bun stdlib bug in
fs.mkdirSync ACL translation on Windows
- An explicit
mode: 0o700-style call inside server-node.mjs that bun's runtime translates to a broken DACL
- A
chmod call after creation that strips inherited ACLs without re-adding the user
grep -n "mkdir\|chmod\|0o7" browse/src/**/*.ts in the gstack source would likely identify the call site.
Bug 2: PTY/terminal-agent sidecar never starts
Repro
- After fixing Bug 1's ACL (manually or otherwise), get the server reachable on its declared port
curl http://127.0.0.1:34567/health returns:
{
"status": "unhealthy",
"mode": "headed",
"uptime": 863,
"tabs": 2,
"token": "...",
"chatEnabled": false,
"security": {...},
"terminalPort": null
}
- Browser control via
browse goto, browse click, browse snapshot all work
- Side Panel extension shows: "Browse server not ready. Reload sidebar to retry. Real PTY. Real terminal. Real claude."
- Reloading the sidebar doesn't help —
terminalPort stays null
Likely cause
The "terminal agent" mentioned in the connect banner (Launching headed Chromium with extension + terminal agent...) is presumably a node-pty / ConPTY-based child process. On Windows, node-pty requires either:
- A native prebuild matching the Node ABI of the embedded Node (the server runs under
C:\Program Files\nodejs\node.exe, which is the user's system Node, not bundled)
- Or ConPTY direct invocation via the Windows API
If server-node.mjs is bundled with a Linux/macOS-compatible PTY implementation but no Windows fallback, terminalPort will never bind and chatEnabled stays false. No log entry surfaces because the failure is swallowed.
Expected behavior
gstack browse connect on Windows creates .gstack/ directories that are at minimum readable/writable by the invoking user
- The terminal-agent sidecar starts on Windows (or gracefully degrades with a visible log message), enabling the Side Panel chat
Workaround for users hitting this
For Bug 1 only (browser control without sidebar chat): after every browse connect, run a PowerShell snippet that rewrites the DACL on .gstack/browse.json so the client can read it. Browser commands (browse goto, click, snapshot) then work for AI-driven control even though the sidebar chat doesn't.
For Bug 2: no workaround found. The PTY sidecar appears to be a hard requirement for the sidebar chat tab.
Related
This effectively makes the gstack sidebar GUI Windows-incompatible in v1.40.0.0. Mentioning because /open-gstack-browser SKILL.md walks users through pinning the extension and opening the Side Panel, which then fails with the confusing "Browse server not ready" message.
Summary
On Windows,
gstack browse connecthas two distinct bugs that together make the sidebar GUI unusable:.gstack/state directory — the directory and its child files are created with an ACL containing only the machine SID (S-1-5-21-<machine>, renders asMACHINE\with empty username and FullControl). Even the directory's owner gets no enumerable access. The nextbrowseinvocation can't read its ownbrowse.json, lock files, orbrowse-startup-error.log.$ browse statusreports "server failed to start" while a fully working server is actually listening./healthreturns{"status":"unhealthy", "chatEnabled":false, "terminalPort":null}. The Side Panel extension then shows "Browse server not ready. Reload sidebar to retry." The chat tab never works.Environment
~/.claude/skills/gstack/package.json)~/.claude/skills/gstack/browse/dist/browse.exe(bun-compiled)~/.claude/skills/gstack/browse/dist/server-node.mjsrunning under Node 22 fromC:\Program Files\nodejs\node.exeBug 1: Broken DACL on .gstack/
Repro
.gstack/and~/.gstack/chromium-profile/browse.exe connectfrom a project directory whose parent has normal ACLs (e.g. user + Administrators + SYSTEM inherited).gstack/directory:Note the malformed entry — username is empty, SID is the machine SID with no RID. There is no ACE for the user that ran the command, even though that user is the directory's Owner. The same broken ACL is applied to every file written into
.gstack/(e.g.browse.json,browse-startup-error.log).Same happens for
~/.gstack/chromium-profile/— the EPERM caught inbrowse-startup-error.logis:Workaround (confirms the diagnosis)
As Owner, rewriting the DACL using .NET
DirectoryInfo.SetAccessControlwithAccessControlSections.Accessonly (avoids needing SeSecurityPrivilege whichSet-Acldemands) fixes enumeration immediately:This needs to be done on
.gstack/AND~/.gstack/AND every file written inside them (the broken ACL is re-applied per-file, not just on the directory).Likely cause
Whatever code creates these directories/files is passing an explicit security descriptor that contains only the machine SID — looks like
mkdir/fs.writeFileis being called with a custom mode/security parameter that maps to a Windows ACL with no user grants. Could be:fs.mkdirSyncACL translation on Windowsmode: 0o700-style call insideserver-node.mjsthat bun's runtime translates to a broken DACLchmodcall after creation that strips inherited ACLs without re-adding the usergrep -n "mkdir\|chmod\|0o7" browse/src/**/*.tsin the gstack source would likely identify the call site.Bug 2: PTY/terminal-agent sidecar never starts
Repro
curl http://127.0.0.1:34567/healthreturns:{ "status": "unhealthy", "mode": "headed", "uptime": 863, "tabs": 2, "token": "...", "chatEnabled": false, "security": {...}, "terminalPort": null }browse goto,browse click,browse snapshotall workterminalPortstaysnullLikely cause
The "terminal agent" mentioned in the connect banner (
Launching headed Chromium with extension + terminal agent...) is presumably a node-pty / ConPTY-based child process. On Windows, node-pty requires either:C:\Program Files\nodejs\node.exe, which is the user's system Node, not bundled)If
server-node.mjsis bundled with a Linux/macOS-compatible PTY implementation but no Windows fallback, terminalPort will never bind and chatEnabled stays false. No log entry surfaces because the failure is swallowed.Expected behavior
gstack browse connecton Windows creates.gstack/directories that are at minimum readable/writable by the invoking userWorkaround for users hitting this
For Bug 1 only (browser control without sidebar chat): after every
browse connect, run a PowerShell snippet that rewrites the DACL on.gstack/browse.jsonso the client can read it. Browser commands (browse goto,click,snapshot) then work for AI-driven control even though the sidebar chat doesn't.For Bug 2: no workaround found. The PTY sidecar appears to be a hard requirement for the sidebar chat tab.
Related
This effectively makes the gstack sidebar GUI Windows-incompatible in v1.40.0.0. Mentioning because
/open-gstack-browserSKILL.md walks users through pinning the extension and opening the Side Panel, which then fails with the confusing "Browse server not ready" message.