Hey! I was looking around for a remote SSH workspace setup for OpenCode, partly because omp (oh-my-pi) has its own built-in remote SSH flow and I figured OpenCode should have something similar. I didn't see anything official in core, so finding this project was really cool. The architecture with the Go stub is super clean.
I spent some time reading through the codebase and testing it out, and ran into a few snags and edge cases between the Go stub and the TypeScript plugin that seemed worth sharing:
- TUI workspace creation: When creating a workspace from OpenCode's TUI (
/warp), extra.provider is not passed in, so resolveProvider currently throws right away. Falling back to the default or first configured provider when extra.provider is omitted gets it through cleanly.
- Stub binary path:
stubPath is resolved relative to process.cwd(). Unless OpenCode happens to be launched directly inside plugin/, it cannot find the binary to upload.
- SSE connection cleanup in the stub:
Events calls h.events.Subscribe(), but there is no unsubscribe mechanism when the client disconnects, and <-ch in defer can block indefinitely. Over multiple reconnects, that can leave channels registered and goroutines hanging.
- Empty pattern check: In
PermissionReply, accessing p.Patterns[0] will panic if Patterns is empty. Adding a quick length check avoids crashing the stub.
- Path traversal and normalization: In
matchPattern, prefix matching happens on raw strings without filepath.Clean or resolving symlinks, so something like /approved/dir/../../etc could match an approved prefix. Also, session and workspace deletion and creation use client-provided IDs directly in filepath.Join, which could allow navigating outside the state directory if an ID has ../.
- Token file in
/tmp: Both ssh.ts and setup-host.sh write auth tokens to static /tmp paths with default permissions. On shared machines, using 0600 permissions or piping the token directly over SSH stdin avoids leaving world-readable token files around.
- Token trailing newline:
os.ReadFile(tokenFile) is not trimmed of whitespace, so if the token file has a trailing newline, authorization headers end up returning 401.
Would you be open to a pull request, or a couple smaller ones, to fix these up? Happy to contribute and test them out if you would like.
Hey! I was looking around for a remote SSH workspace setup for OpenCode, partly because omp (oh-my-pi) has its own built-in remote SSH flow and I figured OpenCode should have something similar. I didn't see anything official in core, so finding this project was really cool. The architecture with the Go stub is super clean.
I spent some time reading through the codebase and testing it out, and ran into a few snags and edge cases between the Go stub and the TypeScript plugin that seemed worth sharing:
/warp),extra.provideris not passed in, soresolveProvidercurrently throws right away. Falling back to the default or first configured provider whenextra.provideris omitted gets it through cleanly.stubPathis resolved relative toprocess.cwd(). Unless OpenCode happens to be launched directly insideplugin/, it cannot find the binary to upload.Eventscallsh.events.Subscribe(), but there is no unsubscribe mechanism when the client disconnects, and<-chindefercan block indefinitely. Over multiple reconnects, that can leave channels registered and goroutines hanging.PermissionReply, accessingp.Patterns[0]will panic ifPatternsis empty. Adding a quick length check avoids crashing the stub.matchPattern, prefix matching happens on raw strings withoutfilepath.Cleanor resolving symlinks, so something like/approved/dir/../../etccould match an approved prefix. Also, session and workspace deletion and creation use client-provided IDs directly infilepath.Join, which could allow navigating outside the state directory if an ID has.././tmp: Bothssh.tsandsetup-host.shwrite auth tokens to static/tmppaths with default permissions. On shared machines, using0600permissions or piping the token directly over SSH stdin avoids leaving world-readable token files around.os.ReadFile(tokenFile)is not trimmed of whitespace, so if the token file has a trailing newline, authorization headers end up returning 401.Would you be open to a pull request, or a couple smaller ones, to fix these up? Happy to contribute and test them out if you would like.