Skip to content

Reverse Port Forwarding (ssh -R) [Spec + Impl] - #47

Merged
rhuss merged 1 commit into
mainfrom
025-reverse-port-forwarding
Aug 8, 2026
Merged

Reverse Port Forwarding (ssh -R) [Spec + Impl]#47
rhuss merged 1 commit into
mainfrom
025-reverse-port-forwarding

Conversation

@rhuss

@rhuss rhuss commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Summary

Implementation of Reverse Port Forwarding (ssh -R) for the OpenShell SDK.

Adds RemoteListen method to TCPInterface that sets up reverse port forwarding
from a sandbox back to the client. Since the upstream proto extension does not yet
exist, this covers the SDK API surface: interface method, functional options, input
validation, real client stub, and fake client with validation parity.

Changes

  • openshell/v1/tcp.go: Added RemoteListen to TCPInterface, RemoteListenOption, WithRemoteBindAddress, WithRemoteListenServiceID
  • openshell/v1/tcp_client.go: Real client stub (validates inputs, returns Unimplemented)
  • openshell/v1/fake/tcp.go: Fake client with validation parity
  • openshell/v1/tcp_client_test.go: Comprehensive tests for real client
  • openshell/v1/fake/tcp_test.go: Comprehensive tests for fake client
  • openshell/v1/doc.go: RemoteListen usage examples

Artifacts

  • Spec: specs/025-reverse-port-forwarding/spec.md
  • Plan: specs/025-reverse-port-forwarding/plan.md
  • Tasks: specs/025-reverse-port-forwarding/tasks.md

Summary by CodeRabbit

  • New Features
    • Added a reverse TCP port forwarding API for directing connections from a sandbox to a local target.
    • Added options to customize the sandbox bind address and audit service identifier.
    • Added validation for sandbox names, ports, and local target addresses, including IPv6 formats.
  • Limitations
    • The operation currently reports “unimplemented” until upstream protocol support is available.
  • Documentation
    • Added usage guidance, specifications, and acceptance criteria for reverse port forwarding.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rhuss, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3da24549-b447-4fe4-970d-dc67e3fc84d9

📥 Commits

Reviewing files that changed from the base of the PR and between bd0909b and 36fc410.

📒 Files selected for processing (2)
  • brainstorm/030-upstream-review-findings.md
  • specs/025-reverse-port-forwarding/SMOKE-TEST.md
📝 Walkthrough

Walkthrough

The PR adds TCPInterface.RemoteListen with bind-address and service-ID options. Real and fake clients validate inputs and return Unimplemented for valid requests. Tests and documentation cover the API. Project metadata also receives two updates.

Changes

Reverse port forwarding SDK

Layer / File(s) Summary
RemoteListen specification and implementation plan
specs/025-reverse-port-forwarding/spec.md, research.md, data-model.md, plan.md, tasks.md, checklists/requirements.md
The specification defines the API, validation, options, lifecycle, deferred gRPC behavior, and implementation tasks.
RemoteListen API contract and documentation
openshell/v1/tcp.go, openshell/v1/doc.go
The SDK adds RemoteListenOption, option constructors, the TCPInterface.RemoteListen method, and package documentation.
Client validation and deferred execution
openshell/v1/tcp_client.go, openshell/v1/fake/tcp.go
The real and fake clients validate sandbox names, ports, and host:port targets. Valid calls return Unimplemented; closed fake clients return Unavailable.
Validation parity and review evidence
openshell/v1/tcp_client_test.go, openshell/v1/fake/tcp_test.go, specs/025-reverse-port-forwarding/REVIEW-CODE.md
Tests cover invalid inputs, boundary ports, IPv6 targets, options, client state, and matching error classifications. The review report records verification results.

Project metadata updates

Layer / File(s) Summary
Local and planning metadata
.gitignore, CLAUDE.md
.mcp.json is ignored, and the referenced SPECKIT plan points to reverse port forwarding.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies reverse port forwarding and accurately indicates that the pull request includes specification and implementation changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 025-reverse-port-forwarding

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.50000% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 89.43%. Comparing base (bac5854) to head (36fc410).

Files with missing lines Patch % Lines
openshell/v1/tcp.go 33.33% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #47      +/-   ##
==========================================
+ Coverage   89.41%   89.43%   +0.02%     
==========================================
  Files          78       78              
  Lines        4901     4932      +31     
==========================================
+ Hits         4382     4411      +29     
  Misses        352      352              
- Partials      167      169       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
openshell/v1/internal/grpc/conn_test.go (1)

15-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Exercise the transport before asserting successful creation.

grpc.NewClient creates an idle client and connects later, so these tests currently only validate argument parsing and construction.

Call conn.Connect() and wait for connectivity.Ready before closing. Add an in-process listener/RPC for the TLS and insecure cases, and capture server-side metadata in TestNewConnectionHTTPWithTokenAuth to verify the authorization header is sent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/internal/grpc/conn_test.go` around lines 15 - 74, Update the
connection tests around NewConnection to exercise the transport by calling
conn.Connect() and waiting for connectivity.Ready before closing. Replace
unreachable TLS and insecure endpoints with in-process listeners and RPC-capable
servers configured for the relevant transport, and in
TestNewConnectionHTTPWithTokenAuth capture server metadata and assert the
authorization header contains the expected token.
openshell/v1/grpc_errors.go (1)

13-20: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Check wrapped context context sentinels before mapping them to ErrorInternal.

contextError only compares the returned error value directly, so fmt.Errorf("wait failed: %w", context.DeadlineExceeded) maps to ErrorInternal. Use errors.Is for both context.DeadlineExceeded and context.Canceled, add tests for wrapped context errors, and update WaitReady to use this helper instead of producing an ErrorInternal message.

Proposed fix
-import "context"
+import (
+	"context"
+	"errors"
+)
...
-switch err {
-case context.DeadlineExceeded:
+switch {
+case errors.Is(err, context.DeadlineExceeded):
...
-case context.Canceled:
+case errors.Is(err, context.Canceled):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/grpc_errors.go` around lines 13 - 20, Update contextError to use
errors.Is when matching context.DeadlineExceeded and context.Canceled so wrapped
sentinels map to their existing deadline and cancellation codes; add coverage
for wrapped errors, and change WaitReady to route failures through contextError
instead of constructing an ErrorInternal result directly.
openshell/v1/fake/tcp_test.go (1)

210-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert option values directly.

This test only proves that the options do not change the returned error. Because fakeTCPClient.RemoteListen ignores options and returns ErrorUnimplemented, the test passes even if either option stops populating its field. Add a package-level test in openshell/v1/tcp_client_test.go that applies both options to remoteListenConfig and asserts bindAddress and serviceID.

The implementation plan requires option tests to verify the populated configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/fake/tcp_test.go` around lines 210 - 218, Add a package-level
test in tcp_client_test.go that applies WithRemoteBindAddress and
WithRemoteListenServiceID to a remoteListenConfig, then directly assert that
bindAddress and serviceID contain the expected values. Keep
TestFakeTCP_RemoteListen_WithOptions focused on the unimplemented error behavior
rather than using it to validate option population.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@openshell/v1/doc.go`:
- Around line 245-264: The RemoteListen documentation currently presents
blocking tunnel behavior even though the real and fake implementations return
ErrorUnimplemented. Update the “Reverse Port Forwarding” section around
RemoteListen to explicitly state that the method remains unavailable until
upstream proto support lands, or clearly label the example as future behavior.

In `@openshell/v1/internal/converter/coverage_test.go`:
- Around line 6-42: Update assertAllFieldsCovered to use Testify assertions
instead of t.Errorf for both uncovered fields and stale handled-field checks,
adding the appropriate testify import while retaining the existing test helper
behavior.
- Around line 16-42: Update assertAllFieldsCovered to validate every name in
skipped against msg.Fields(), mirroring the existing stale-entry validation for
handled. Report skipped names that no longer exist in the proto descriptor while
preserving the current coverage checks.

In `@openshell/v1/internal/converter/network_policy.go`:
- Around line 314-331: Update mcpOptionsFromProto and mcpOptionsToProto to
deep-copy each non-nil boolean pointer instead of reusing the source pointers,
while preserving nil values and existing nil-object handling. Add tests that
mutate either converted MCP options object and verify the original policy
object's boolean fields remain unchanged in both conversion directions.

In `@openshell/v1/internal/grpc/conn_test.go`:
- Around line 6-13: Update the tests in conn_test.go to use testify assertions
consistently: replace each t.Fatalf error check with require.NoError or
assert.NoError as appropriate, adding the testify assertion import and
preserving the existing failure behavior.

In `@openshell/v1/sandbox_client.go`:
- Line 241: Update the asynchronous receive-error path in Watch so the recvErr
assigned to EventError.Err is passed through converter.FromGRPCError, matching
the initial stream.Recv error handling; add coverage for a post-event
codes.Unavailable error and assert the published event satisfies IsUnavailable.

In `@openshell/v1/tcp_client.go`:
- Around line 88-104: Update tcpClient.RemoteListen to check the client
lifecycle state after validating inputs and return the established Unavailable
status when t is closed, before the existing Unimplemented fallback. Add a
regression test using a real client that closes it, invokes RemoteListen with
valid arguments, and verifies the Unavailable result.

In `@specs/025-reverse-port-forwarding/REVIEW-CODE.md`:
- Line 180: Update the fenced test-output block at the referenced Markdown
section to include the text language tag, preserving its existing command-output
contents.

---

Nitpick comments:
In `@openshell/v1/fake/tcp_test.go`:
- Around line 210-218: Add a package-level test in tcp_client_test.go that
applies WithRemoteBindAddress and WithRemoteListenServiceID to a
remoteListenConfig, then directly assert that bindAddress and serviceID contain
the expected values. Keep TestFakeTCP_RemoteListen_WithOptions focused on the
unimplemented error behavior rather than using it to validate option population.

In `@openshell/v1/grpc_errors.go`:
- Around line 13-20: Update contextError to use errors.Is when matching
context.DeadlineExceeded and context.Canceled so wrapped sentinels map to their
existing deadline and cancellation codes; add coverage for wrapped errors, and
change WaitReady to route failures through contextError instead of constructing
an ErrorInternal result directly.

In `@openshell/v1/internal/grpc/conn_test.go`:
- Around line 15-74: Update the connection tests around NewConnection to
exercise the transport by calling conn.Connect() and waiting for
connectivity.Ready before closing. Replace unreachable TLS and insecure
endpoints with in-process listeners and RPC-capable servers configured for the
relevant transport, and in TestNewConnectionHTTPWithTokenAuth capture server
metadata and assert the authorization header contains the expected token.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 62d441d2-2248-4431-8786-4e58693e3c0f

📥 Commits

Reviewing files that changed from the base of the PR and between f7b4752 and 6695de4.

📒 Files selected for processing (28)
  • .gitignore
  • CLAUDE.md
  • openshell/v1/config_client_test.go
  • openshell/v1/doc.go
  • openshell/v1/errors_test.go
  • openshell/v1/fake/tcp.go
  • openshell/v1/fake/tcp_test.go
  • openshell/v1/grpc_errors.go
  • openshell/v1/internal/converter/coverage_test.go
  • openshell/v1/internal/converter/errors.go
  • openshell/v1/internal/converter/network_policy.go
  • openshell/v1/internal/grpc/conn.go
  • openshell/v1/internal/grpc/conn_test.go
  • openshell/v1/sandbox_client.go
  • openshell/v1/tcp.go
  • openshell/v1/tcp_client.go
  • openshell/v1/tcp_client_test.go
  • openshell/v1/types/errors.go
  • openshell/v1/types/network_policy.go
  • openshell/v1/types/types.go
  • openshell/v1/types/watch.go
  • specs/025-reverse-port-forwarding/REVIEW-CODE.md
  • specs/025-reverse-port-forwarding/checklists/requirements.md
  • specs/025-reverse-port-forwarding/data-model.md
  • specs/025-reverse-port-forwarding/plan.md
  • specs/025-reverse-port-forwarding/research.md
  • specs/025-reverse-port-forwarding/spec.md
  • specs/025-reverse-port-forwarding/tasks.md

Comment thread openshell/v1/doc.go
Comment on lines +6 to +42
import (
"testing"

pb "github.com/rhuss/openshell-sdk-go/proto/openshellv1"
sbv1 "github.com/rhuss/openshell-sdk-go/proto/sandboxv1"
"google.golang.org/protobuf/reflect/protoreflect"
)

type fieldSet map[string]bool

func assertAllFieldsCovered(t *testing.T, msg protoreflect.MessageDescriptor, handled, skipped fieldSet) {
t.Helper()

fields := msg.Fields()
for i := 0; i < fields.Len(); i++ {
name := string(fields.Get(i).Name())
if handled[name] {
continue
}
if skipped[name] {
continue
}
t.Errorf("proto field %s.%s is not handled by the converter", msg.FullName(), name)
}

for name := range handled {
found := false
for i := 0; i < fields.Len(); i++ {
if string(fields.Get(i).Name()) == name {
found = true
break
}
}
if !found {
t.Errorf("handled field %q does not exist in proto %s (stale entry)", name, msg.FullName())
}
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use Testify assertions in this test.

This file only uses t.Errorf. Update assertAllFieldsCovered to use assert or require.

As per coding guidelines, “Go tests must use the Go testing package with testify assertions (assert/require).”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/internal/converter/coverage_test.go` around lines 6 - 42, Update
assertAllFieldsCovered to use Testify assertions instead of t.Errorf for both
uncovered fields and stale handled-field checks, adding the appropriate testify
import while retaining the existing test helper behavior.

Source: Coding guidelines

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. The helper uses t.Errorf while the project convention requires testify assertions. Will update to use assert/require.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhuss, acknowledged. The finding remains open until the helper uses Testify assertions.

You are interacting with an AI system.

Comment on lines +16 to +42
func assertAllFieldsCovered(t *testing.T, msg protoreflect.MessageDescriptor, handled, skipped fieldSet) {
t.Helper()

fields := msg.Fields()
for i := 0; i < fields.Len(); i++ {
name := string(fields.Get(i).Name())
if handled[name] {
continue
}
if skipped[name] {
continue
}
t.Errorf("proto field %s.%s is not handled by the converter", msg.FullName(), name)
}

for name := range handled {
found := false
for i := 0; i < fields.Len(); i++ {
if string(fields.Get(i).Name()) == name {
found = true
break
}
}
if !found {
t.Errorf("handled field %q does not exist in proto %s (stale entry)", name, msg.FullName())
}
}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Validate stale skipped entries.

The helper validates stale names only in handled. A removed or renamed skipped proto field remains silently accepted. Validate every skipped name against the descriptor too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/internal/converter/coverage_test.go` around lines 16 - 42,
Update assertAllFieldsCovered to validate every name in skipped against
msg.Fields(), mirroring the existing stale-entry validation for handled. Report
skipped names that no longer exist in the proto descriptor while preserving the
current coverage checks.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Stale entries in the skipped list are silently accepted. Will add validation against the proto descriptor.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhuss, acknowledged. Validating skipped names against msg.Fields() will prevent stale exclusions from hiding removed or renamed proto fields.

You are interacting with an AI system.

Comment thread openshell/v1/internal/converter/network_policy.go Outdated
Comment thread openshell/v1/internal/grpc/conn_test.go
Comment thread openshell/v1/sandbox_client.go Outdated
Comment thread openshell/v1/tcp_client.go Outdated
Comment on lines +88 to +104
func (t *tcpClient) RemoteListen(_ context.Context, _, sandboxName string, remotePort uint32, localTarget string, _ ...RemoteListenOption) error {
if sandboxName == "" {
return &StatusError{Code: ErrorInvalidArgument, Message: "sandbox name must not be empty"}
}
if remotePort == 0 || remotePort > 65535 {
return &StatusError{
Code: ErrorInvalidArgument,
Message: fmt.Sprintf("port must be in range 1-65535, got %d", remotePort),
}
}
if _, _, err := net.SplitHostPort(localTarget); err != nil {
return &StatusError{
Code: ErrorInvalidArgument,
Message: fmt.Sprintf("invalid localTarget %q: %v", localTarget, err),
}
}
return &StatusError{Code: ErrorUnimplemented, Message: "RemoteListen requires upstream proto extension (not yet available)"}

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Return Unavailable after the real client closes.

RemoteListen does not inspect t or issue an RPC. A closed client with valid input therefore always returns Unimplemented. This violates the specified closed-client contract and breaks real/fake validation parity.

Use the client lifecycle state before returning Unimplemented. Add a real-client regression test for the closed state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openshell/v1/tcp_client.go` around lines 88 - 104, Update
tcpClient.RemoteListen to check the client lifecycle state after validating
inputs and return the established Unavailable status when t is closed, before
the existing Unimplemented fallback. Add a regression test using a real client
that closes it, invokes RemoteListen with valid arguments, and verifies the
Unavailable result.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. RemoteListen should check the client lifecycle state and return Unavailable when closed, before the Unimplemented fallback. This maintains fake-real parity per project invariants.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhuss, acknowledged. The finding remains valid.

RemoteListen must check the client lifecycle state after input validation. If the client is closed, it must return Unavailable before the Unimplemented fallback. The regression test must verify this real-client behavior.

🐇


✏️ Learnings added
Learnt from: rhuss
Repo: rhuss/openshell-sdk-go PR: 47
File: openshell/v1/tcp_client.go:88-104
Timestamp: 2026-08-08T05:37:54.808Z
Learning: In the OpenShell Go SDK, `tcpClient.RemoteListen` must preserve fake/real client parity: after valid argument validation, it must return the established `Unavailable` status when the client is closed, before it returns `ErrorUnimplemented` for unavailable upstream protocol support.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.


### Post-Fix Verification

```

@coderabbitai coderabbitai Bot Aug 5, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced test-output block.

markdownlint-cli2 reports MD040 at Line [180] because the fenced block has no language. Use text for this command-output block.

🧰 Tools
🪛 markdownlint-cli2 (0.23.2)

[warning] 180-180: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specs/025-reverse-port-forwarding/REVIEW-CODE.md` at line 180, Update the
fenced test-output block at the referenced Markdown section to include the text
language tag, preserving its existing command-output contents.

Source: Linters/SAST tools

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. Will add the text language tag.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rhuss, acknowledged. Adding the text language tag will resolve MD040 for the command-output block.

You are interacting with an AI system.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds the SDK API surface for reverse port forwarding while upstream protocol support remains unavailable.

Changes:

  • Adds RemoteListen, options, validation, and real/fake stubs.
  • Adds unit tests and usage documentation.
  • Adds feature specifications, plans, and review artifacts.

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
.gitignore Ignores local MCP configuration.
CLAUDE.md Points to the current feature plan.
openshell/v1/tcp.go Defines the new API and options.
openshell/v1/tcp_client.go Adds the real-client stub and validation.
openshell/v1/tcp_client_test.go Tests real-client validation and stub behavior.
openshell/v1/fake/tcp.go Adds fake-client validation and stub behavior.
openshell/v1/fake/tcp_test.go Tests fake-client behavior and validation.
openshell/v1/doc.go Documents reverse forwarding usage.
specs/025-reverse-port-forwarding/spec.md Defines requirements and scenarios.
specs/025-reverse-port-forwarding/plan.md Describes implementation strategy.
specs/025-reverse-port-forwarding/tasks.md Tracks implementation tasks.
specs/025-reverse-port-forwarding/research.md Records design decisions.
specs/025-reverse-port-forwarding/data-model.md Documents API entities and relationships.
specs/025-reverse-port-forwarding/REVIEW-CODE.md Records the prior compliance review.
specs/025-reverse-port-forwarding/checklists/requirements.md Tracks specification readiness.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread openshell/v1/tcp_client.go Outdated
Comment thread openshell/v1/doc.go
Comment thread specs/025-reverse-port-forwarding/REVIEW-CODE.md
Comment thread specs/025-reverse-port-forwarding/spec.md
Comment thread specs/025-reverse-port-forwarding/tasks.md
rhuss added a commit that referenced this pull request Aug 8, 2026
- Fix fake Forward parity: add sandboxName validation (was missing,
  violating fake-real parity invariant)
- Name context parameter in real RemoteListen stub (prevents future
  implementation from discarding it)
- Add error message content assertions to RemoteListen tests
- Add bare IPv6 malformed target case to fake parity test

Assisted-By: 🤖 Claude Code
rhuss added a commit that referenced this pull request Aug 8, 2026
Adds reverse port forwarding review findings (fake Forward parity,
context parameter, bindAddress default, Close error surfacing,
RemoteListen return type design, closed-client detection pattern)
to the upstream contribution checklist.

Assisted-By: 🤖 Claude Code
Add RemoteListen method to TCPInterface enabling reverse port forwarding
from sandbox to client (ssh -R equivalent). Includes functional options
(WithRemoteBindAddress, WithRemoteListenServiceID), input validation,
fake client support with Unimplemented error, and comprehensive tests.
Real gRPC implementation deferred pending upstream proto extension.
@rhuss
rhuss force-pushed the 025-reverse-port-forwarding branch from 9c276d2 to 36fc410 Compare August 8, 2026 08:58
@rhuss
rhuss merged commit bd6e7e7 into main Aug 8, 2026
6 of 7 checks passed
@rhuss
rhuss deleted the 025-reverse-port-forwarding branch August 8, 2026 09:07
rhuss added a commit that referenced this pull request Aug 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants