Skip to content

Support Tailscale HTTPS Executor access - #18

Merged
bmdavis419 merged 2 commits into
mainfrom
agent/tailscale-executor
Jul 9, 2026
Merged

Support Tailscale HTTPS Executor access#18
bmdavis419 merged 2 commits into
mainfrom
agent/tailscale-executor

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What changed

  • persist per-box Executor public origin, fixed publish address, and DNS across create, import, and upgrade
  • pass EXECUTOR_WEB_BASE_URL and Docker DNS into the Executor container
  • make list, open, and doctor understand and verify the configured HTTPS origin
  • add tx9 gateway status|enable|disable; enabling rehomes any foreground gateway under TX9 supervision while preserving the single-writer gate
  • add a complete Tailscale Serve + Executor guide covering MagicDNS, public DNS, OAuth callbacks, multiple boxes, validation, troubleshooting, and reset instructions
  • retain the existing defaults for users who do not opt in, with --clear-executor-config available to remove persisted settings

Why

Executor previously advertised http://localhost:4788 for OAuth callbacks while TX9 exposed a random raw HTTP host port. Recreating the container could change that port, and the container did not inherit MagicDNS. Hermes setup could also leave a foreground gateway attached to tmux, so closing the pane took it offline.

This gives reverse proxies such as Tailscale Serve a stable loopback target and exact HTTPS callback origin while preserving TX9's agent/Executor network isolation.

Validation

  • PATH=/home/davis/.local/go/bin:$PATH make check
  • git diff --check
  • verified the guide's MagicDNS and upstream-DNS discovery commands on Nexus
  • verified every referenced Executor and Tailscale documentation link returns HTTP 200

Note

Add Tailscale HTTPS access support for the Executor with persisted configuration

  • Adds --executor-web-base-url, --executor-publish, --executor-dns, and --clear-executor-config flags to tx9 create, tx9 import, and tx9 upgrade, persisting executor config across upgrades via executor_config.go.
  • Adds a new tx9 gateway status|enable|disable command in cmd_gateway.go to manage the supervised Hermes gateway; enable requires explicit single-writer confirmation.
  • tx9 list and tx9 open now surface the configured public HTTPS URL (from the tx9.executor-web-base-url Docker label) instead of the raw host:port.
  • tx9 doctor probes the executor's /api/health endpoint when a public base URL is configured and fails if it is unreachable.
  • Executor containers can now be created with a fixed host IP/port binding and custom DNS resolvers via create.go and docker/client.go.
  • Behavioral Change: tx9 open now returns an error if the configured public base URL is invalid, whereas previously it always succeeded.

Macroscope summarized 9e59f6c.

Greptile Summary

This PR adds Tailscale-friendly HTTPS support for Executor access. The main changes are:

  • Persisted per-box Executor public origin, publish address, and DNS settings.
  • New Executor config flags for create, import, and box-specific upgrade.
  • Updated list, open, and doctor behavior for configured HTTPS origins.
  • New tx9 gateway status|enable|disable lifecycle commands.
  • A new Tailscale Serve guide for OAuth callbacks, DNS, validation, and troubleshooting.

Confidence Score: 4/5

Mostly safe, with one contained bug in tx9 doctor for non-loopback publish addresses.

The main create/import/upgrade and URL plumbing is well-scoped, but the new configurable publish address is not fully honored by the doctor host probe.

internal/cli/cmd_doctor.go

T-Rex T-Rex Logs

What T-Rex did

  • Reproduced verification that the probe uses the wrong address by binding a non-loopback HTTP server to 169.254.0.21 and exercising probeHostPort; a direct request to the bound endpoint returned HTTP 200 OK, while probing the internal URL on 127.0.0.1 failed with connection refused.
  • Validated repository state by reviewing a sequence of validation logs, which show the initial make check was blocked by missing shellcheck, the fallback Go tests passed for internal/box, internal/cli, and internal/docker, the syntax check target passed, the whitespace check passed, and CLI help transcripts were produced.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
internal/cli/cmd_doctor.go Adds public URL health probing, but the existing host probe still assumes loopback even for non-loopback fixed publish addresses.
internal/box/executor_config.go Adds parsing, normalization, precedence, and persistence for per-box Executor configuration.
internal/box/create.go Passes Executor web base URL, publish binding, DNS, and labels into created executor containers.
internal/cli/cmd_gateway.go Adds host CLI control for Hermes gateway status, enable, and disable through hb.
internal/cli/cmd_upgrade.go Applies persisted or overridden Executor configuration when recreating box containers during upgrade.
docs/tailscale-executor.md Adds a guide for Tailscale Serve, DNS, OAuth callback, and gateway lifecycle setup.

Comments Outside Diff (2)

  1. internal/cli/cmd_doctor.go, line 56 (link)

    P1 Use configured bind host
    probeHostPort always checks 127.0.0.1, but --executor-publish accepts any IPv4 bind address. A box recreated with --executor-publish 100.64.1.2:32770 publishes the executor only on that host IP, so tx9 doctor fails the local probe even though Docker published the configured endpoint correctly. The probe needs the published HostIP from Docker, not just HostPort.

    Artifacts

    Repro: focused Go test binding executor endpoint to a non-loopback HostIP and invoking probeHostPort

    • Contains supporting evidence from the run (text/x-go; charset=utf-8).

    Repro: verbose go test output showing configured endpoint status 200 OK and doctor probe failure on 127.0.0.1

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/cli/cmd_doctor.go
    Line: 56
    
    Comment:
    **Use configured bind host**
    `probeHostPort` always checks `127.0.0.1`, but `--executor-publish` accepts any IPv4 bind address. A box recreated with `--executor-publish 100.64.1.2:32770` publishes the executor only on that host IP, so `tx9 doctor` fails the local probe even though Docker published the configured endpoint correctly. The probe needs the published `HostIP` from Docker, not just `HostPort`.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

  2. internal/cli/cmd_doctor.go, line 56 (link)

    P1 Probe uses wrong address
    --executor-publish accepts any IPv4 IP:port, but doctor still probes 127.0.0.1:<port> after HostPort drops the published HostIP. A valid box published to a non-loopback address, such as a LAN or Tailscale IP, will run correctly while tx9 doctor always fails the host probe. Please preserve and probe Docker's published host IP instead of assuming loopback.

    Artifacts

    Repro: focused Go test harness that binds a non-loopback HTTP server, performs a direct request, and invokes probeHostPort

    • Contains supporting evidence from the run (text/x-go; charset=utf-8).

    Repro: go test output showing direct HTTP 200 OK on the bound IP and probeHostPort failure against 127.0.0.1

    • Keeps the command output available without making the summary code-heavy.

    View artifacts

    T-Rex Ran code and verified through T-Rex

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: internal/cli/cmd_doctor.go
    Line: 56
    
    Comment:
    **Probe uses wrong address**
    `--executor-publish` accepts any IPv4 `IP:port`, but `doctor` still probes `127.0.0.1:<port>` after `HostPort` drops the published `HostIP`. A valid box published to a non-loopback address, such as a LAN or Tailscale IP, will run correctly while `tx9 doctor` always fails the host probe. Please preserve and probe Docker's published host IP instead of assuming loopback.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Codex

Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
internal/cli/cmd_doctor.go:56
**Probe uses wrong address**
`--executor-publish` accepts any IPv4 `IP:port`, but `doctor` still probes `127.0.0.1:<port>` after `HostPort` drops the published `HostIP`. A valid box published to a non-loopback address, such as a LAN or Tailscale IP, will run correctly while `tx9 doctor` always fails the host probe. Please preserve and probe Docker's published host IP instead of assuming loopback.

Reviews (2): Last reviewed commit: "fix: address PR #18 review-bot findings" | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds per-box executor origin and network configuration, wires it through create/import/upgrade, introduces tx9 gateway status|enable|disable, updates dashboard URL handling and doctor health checks, and expands the documentation for the Tailscale-based HTTPS setup.

Changes

Executor configurable origin and gateway CLI

Layer / File(s) Summary
Executor config data and persistence
internal/box/executor_config.go, internal/box/executor_config_test.go, internal/cli/executor_config.go, internal/cli/executor_config_test.go
Adds executor config types, resolution and persistence helpers, CLI flag loading, and tests for parsing and override behavior.
Box URL, labels, and container creation
internal/box/box.go, internal/box/create.go, internal/docker/client.go, internal/docker/labels.go
Stores executor web-base URL on boxes, reads the label during list, updates dashboard/open URL helpers, and extends container creation with executor env, labels, DNS, and port bindings.
Create/import/upgrade executor config wiring
internal/cli/cmd_create.go, internal/cli/cmd_import.go, internal/cli/cmd_upgrade.go
Loads per-box executor config, persists it with the token, passes it into box creation, and updates the next-step guidance.
Gateway CLI command
internal/cli/cmd_gateway.go, internal/cli/cmd_gateway_test.go, internal/cli/dispatch.go
Adds tx9 gateway action handling, confirmation gating for enable, dispatch registration, and command tests.
Dashboard listing, open URL, and doctor probe
internal/cli/cmd_list.go, internal/cli/cmd_open.go, internal/cli/cmd_doctor.go, internal/cli/cmd_doctor_test.go
Passes the executor web-base URL into URL generation and adds a public health probe to doctor with tests.
Executor networking and gateway docs
README.md, docs/docker-architecture.md, docs/tx9-cli-design.md, docs/tailscale-executor.md
Updates the networking model text, documents the gateway command surface, and adds the Tailscale HTTPS setup guide.
Possibly related PRs
- davis7dotsh/tx9#2: Introduces the hb gateway-enable/gateway-disable/status behaviors that the new tx9 gateway command wraps.
- davis7dotsh/tx9#13: Extends the CLI and box URL handling that this PR builds on with executor-origin and gateway wiring.
- davis7dotsh/tx9#16: Also changes the URL origin fallback path used by dashboard and open URL generation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
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.
Title check ✅ Passed The title clearly summarizes the main change: adding Tailscale HTTPS access for the Executor.
Description check ✅ Passed The description matches the PR scope by describing persisted executor config, gateway lifecycle, and Tailscale setup.

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

Comment thread internal/cli/cmd_gateway.go
Comment thread internal/box/executor_config.go
Comment thread internal/cli/executor_config.go
Comment thread internal/box/create.go
Version: ver,
}

if !opts.NoStart {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High box/create.go:143

When Start fails after both containers are created, Create returns the error without removing the containers — the agent and executor are left behind in created state. This PR makes the failure path reachable in practice: opts.Executor.PublishPort is honored at create time, so Docker accepts the container, then Start fails if that host port is already bound. On the upgrade path the caller does not invoke Destroy on this error, so the newly created containers leak and must be removed manually before retrying. The agent-create failure path already best-effort removes the leaked container; the start-failure path at line 144 needs the same cleanup before returning.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/box/create.go around line 143:

When `Start` fails after both containers are created, `Create` returns the error without removing the containers — the agent and executor are left behind in `created` state. This PR makes the failure path reachable in practice: `opts.Executor.PublishPort` is honored at create time, so Docker accepts the container, then `Start` fails if that host port is already bound. On the upgrade path the caller does not invoke `Destroy` on this error, so the newly created containers leak and must be removed manually before retrying. The agent-create failure path already best-effort removes the leaked container; the start-failure path at line 144 needs the same cleanup before returning.

@bmdavis419
bmdavis419 marked this pull request as ready for review July 9, 2026 10:24

@devin-ai-integration devin-ai-integration 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.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Open in Devin Review

@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: 3

🧹 Nitpick comments (1)
internal/box/executor_config_test.go (1)

104-111: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider testing the on-disk delete path for --clear-executor-config.

This case only checks the in-memory LoadExecutorConfig{Clear: true} result. It doesn't verify that calling SaveExecutorConfig with the cleared config actually removes the previously-persisted TX9_EXECUTOR_* keys from the box env file (the setOrDelete delete branch in executor_config.go), which is the behavior --clear-executor-config users actually rely on.

Suggested addition
if err := SaveExecutorConfig(name, "secret-token", cfg); err != nil {
    t.Fatal(err)
}
env, err = state.ReadBoxEnv(name)
if err != nil {
    t.Fatal(err)
}
if _, ok := env[ExecutorWebBaseURLEnv]; ok {
    t.Error("cleared WebBaseURL key was not removed from box env")
}
if _, ok := env[ExecutorPublishEnv]; ok {
    t.Error("cleared publish key was not removed from box env")
}
if _, ok := env[ExecutorDNSEnv]; ok {
    t.Error("cleared DNS key was not removed from box env")
}
🤖 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 `@internal/box/executor_config_test.go` around lines 104 - 111, The test for
LoadExecutorConfig with Clear: true only verifies the in-memory cleared config
and misses the on-disk delete behavior in SaveExecutorConfig/setOrDelete. Extend
the existing executor_config_test case to save the cleared config back through
SaveExecutorConfig, then read the box env and assert the previously persisted
ExecutorWebBaseURLEnv, ExecutorPublishEnv, and ExecutorDNSEnv keys are removed.
Use LoadExecutorConfig, SaveExecutorConfig, and state.ReadBoxEnv to exercise the
full --clear-executor-config path.
🤖 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 `@docs/tx9-cli-design.md`:
- Line 49: The command-surface table entry for tx9 gateway currently uses
unescaped pipe characters, which will split the Markdown row into extra columns.
Update the table row in the docs/tx9-cli-design.md command list so the command
text renders as a single cell, either by escaping the pipe characters or
rephrasing the tx9 gateway <status|enable|disable> <box> syntax. Keep the fix
local to the affected table row.

In `@internal/box/box.go`:
- Around line 300-308: OpenURL currently swallows failures from url.Parse and
returns an empty string, which hides malformed webBaseURL problems from tx9
open. Update OpenURL to return both the URL string and an error, propagate the
parse error instead of discarding it, and adjust the caller(s) that use OpenURL
directly so they can handle and report the failure rather than opening a blank
target.

In `@internal/cli/cmd_doctor.go`:
- Around line 61-63: `probeExecutorPublicURL` is using a non-context-aware HTTP
call and triggers the `noctx` lint error; update it to accept `ctx` and build
the request with that context before calling the client so the probe can be
canceled. Propagate the new signature from the `cmd_doctor` probe call site, and
update the matching test call in `cmd_doctor_test.go` to pass a context (for
example `context.Background()`) so the API stays consistent.

---

Nitpick comments:
In `@internal/box/executor_config_test.go`:
- Around line 104-111: The test for LoadExecutorConfig with Clear: true only
verifies the in-memory cleared config and misses the on-disk delete behavior in
SaveExecutorConfig/setOrDelete. Extend the existing executor_config_test case to
save the cleared config back through SaveExecutorConfig, then read the box env
and assert the previously persisted ExecutorWebBaseURLEnv, ExecutorPublishEnv,
and ExecutorDNSEnv keys are removed. Use LoadExecutorConfig, SaveExecutorConfig,
and state.ReadBoxEnv to exercise the full --clear-executor-config path.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c6ea6349-cb81-4968-b876-9292e0d50a30

📥 Commits

Reviewing files that changed from the base of the PR and between 73f7eba and 3e0b74e.

📒 Files selected for processing (23)
  • README.md
  • docs/docker-architecture.md
  • docs/tailscale-executor.md
  • docs/tx9-cli-design.md
  • internal/box/box.go
  • internal/box/box_test.go
  • internal/box/create.go
  • internal/box/executor_config.go
  • internal/box/executor_config_test.go
  • internal/cli/cmd_create.go
  • internal/cli/cmd_doctor.go
  • internal/cli/cmd_doctor_test.go
  • internal/cli/cmd_gateway.go
  • internal/cli/cmd_gateway_test.go
  • internal/cli/cmd_import.go
  • internal/cli/cmd_list.go
  • internal/cli/cmd_open.go
  • internal/cli/cmd_upgrade.go
  • internal/cli/dispatch.go
  • internal/cli/executor_config.go
  • internal/cli/executor_config_test.go
  • internal/docker/client.go
  • internal/docker/labels.go

Comment thread docs/tx9-cli-design.md Outdated
Comment thread internal/box/box.go Outdated
Comment thread internal/cli/cmd_doctor.go Outdated
Comment on lines +61 to +63
var publicProbeErr error
if b.ExecutorWebBaseURL != "" {
publicProbeErr = probeExecutorPublicURL(b.ExecutorWebBaseURL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix noctx lint violation: thread ctx through probeExecutorPublicURL.

Static analysis flags client.Get(u.String()) (Line 88) — use Do(*http.Request) with a context-aware request instead. ctx is already available in the enclosing closure; thread it through so the probe is cancelable and lint-clean.

🔧 Proposed fix
-		var publicProbeErr error
 		if b.ExecutorWebBaseURL != "" {
-			publicProbeErr = probeExecutorPublicURL(b.ExecutorWebBaseURL)
+			publicProbeErr = probeExecutorPublicURL(ctx, b.ExecutorWebBaseURL)
 			if publicProbeErr == nil {
-func probeExecutorPublicURL(baseURL string) error {
+func probeExecutorPublicURL(ctx context.Context, baseURL string) error {
 	u, err := url.Parse(baseURL)
 	if err != nil {
 		return fmt.Errorf("executor public URL %q is invalid: %w", baseURL, err)
 	}
 	u.Path = "/api/health"
 	u.RawPath = ""
 	u.RawQuery = ""
 	u.Fragment = ""

 	client := &http.Client{Timeout: 10 * time.Second}
-	resp, err := client.Get(u.String())
+	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
+	if err != nil {
+		return fmt.Errorf("executor public health probe %s: %w", u, err)
+	}
+	resp, err := client.Do(req)
 	if err != nil {
 		return fmt.Errorf("executor public health probe %s: %w", u, err)
 	}

The test call site in cmd_doctor_test.go needs a matching update:

if err := probeExecutorPublicURL(context.Background(), server.URL); err != nil {

Also applies to: 77-101

🤖 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 `@internal/cli/cmd_doctor.go` around lines 61 - 63, `probeExecutorPublicURL` is
using a non-context-aware HTTP call and triggers the `noctx` lint error; update
it to accept `ctx` and build the request with that context before calling the
client so the probe can be canceled. Propagate the new signature from the
`cmd_doctor` probe call site, and update the matching test call in
`cmd_doctor_test.go` to pass a context (for example `context.Background()`) so
the API stays consistent.

Source: Linters/SAST tools

Macroscope:
- gateway status/disable no longer require the host-side token cache;
  only enable hard-fails without it (disable is the recovery path)
- reject web base URLs with a dangling colon (https://host:) or an
  empty hostname (https://:8443) in normalizeWebBaseURL
- create/import ignore a stale persisted ~/.tx9/boxes/<name>.env from a
  previous same-named box (new IgnoreStored override; upgrade still
  inherits persisted settings)
- upgrade distinguishes created-but-not-started containers from
  not-recreated ones and says how to recover (labeled containers are
  removed by a retry; no manual docker cleanup)

CodeRabbit:
- escape pipes in the tx9 gateway command-table row
- OpenURL returns (string, error) instead of swallowing url.Parse
  failures into an empty string
- thread ctx through probeExecutorPublicURL (noctx; cancelable probe)
- test that --clear-executor-config deletes persisted TX9_EXECUTOR_*
  keys on disk, not just in memory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@bmdavis419

Copy link
Copy Markdown
Contributor Author

Addressed the review-bot findings in 9e59f6c — all 7 inline findings were fixed (none skipped):

Macroscope

  • cmd_gateway.go: status/disable no longer hard-require the host-side token cache; only enable does. Empty BOXD_EXECUTOR_TOKEN is safe for those paths — hb's token resolution treats empty as unset and status/gateway-disable never contact the executor.
  • executor_config.go: normalizeWebBaseURL now rejects a dangling colon (https://host:) and also an empty hostname (https://:8443), which the original u.Host != "" check missed.
  • stale-env inheritance: create/import now resolve executor config with a new IgnoreStored mode, so a leftover ~/.tx9/boxes/<name>.env from a deleted same-named box can't silently leak into a new one. upgrade keeps the persisted-values fallback — that's its documented purpose.
  • create.go start-failure "leak": partially disputed — the containers are labeled, so re-running the upgrade removes them via removeBoxContainers, and Create intentionally returns the box so the caller knows they exist. Fixed the real gap instead: upgrade's error message claimed "box left without containers" even when they were created but failed to start; it now distinguishes the two cases and says how to recover.

CodeRabbit

  • escaped the | pipes in the tx9 gateway command-table row
  • OpenURL returns (string, error) instead of collapsing a bad configured base URL into an empty string
  • probeExecutorPublicURL takes ctx via http.NewRequestWithContext (noctx)
  • added the suggested test that --clear-executor-config actually deletes the persisted TX9_EXECUTOR_* keys on disk

make check (sh syntax + shellcheck + regression suites + go vet/build/test) passes.

@bmdavis419
bmdavis419 merged commit c215c75 into main Jul 9, 2026
4 checks passed
return fmt.Errorf("upgrade %s: %w", name, err)
}
fromVersion := b.Version
executorConfig, err := executorFlags.load(name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium cli/cmd_upgrade.go:53

cmdUpgrade accepts --executor-web-base-url without requiring a fixed --executor-publish address, so after the upgrade Docker assigns a new random host port for the executor while the box advertises the persisted HTTPS origin. Any reverse proxy pointing at the old port becomes stale and the new public URL stops working. Consider rejecting this combination (or falling back to the previously-bound port) so the advertised origin stays reachable.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/cli/cmd_upgrade.go around line 53:

`cmdUpgrade` accepts `--executor-web-base-url` without requiring a fixed `--executor-publish` address, so after the upgrade Docker assigns a new random host port for the executor while the box advertises the persisted HTTPS origin. Any reverse proxy pointing at the old port becomes stale and the new public URL stops working. Consider rejecting this combination (or falling back to the previously-bound port) so the advertised origin stays reachable.

return fmt.Errorf("import %s: %w", name, err)
}
if err := state.WriteBoxEnv(name, map[string]string{"EXECUTOR_MCP_TOKEN": tok}); err != nil {
if err := box.SaveExecutorConfig(name, tok, executorConfig); err != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium cli/cmd_import.go:133

cmdImport calls executorFlags.loadFresh(name) to treat a leftover ~/.tx9/boxes/<name>.env as fresh, but then calls box.SaveExecutorConfig which internally calls state.ReadBoxEnv(name). If that stale file is unreadable (e.g. bad permissions or ownership left by a previous failed create/import), import aborts with a state.ReadBoxEnv error instead of proceeding, so the operator must manually fix or remove the old env file before a new import can succeed. Consider having SaveExecutorConfig handle a missing/unreadable env file by falling back to an empty map, or having cmdImport pass the already-resolved config so the file isn't re-read.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/cli/cmd_import.go around line 133:

`cmdImport` calls `executorFlags.loadFresh(name)` to treat a leftover `~/.tx9/boxes/<name>.env` as fresh, but then calls `box.SaveExecutorConfig` which internally calls `state.ReadBoxEnv(name)`. If that stale file is unreadable (e.g. bad permissions or ownership left by a previous failed create/import), import aborts with a `state.ReadBoxEnv` error instead of proceeding, so the operator must manually fix or remove the old env file before a new import can succeed. Consider having `SaveExecutorConfig` handle a missing/unreadable env file by falling back to an empty map, or having `cmdImport` pass the already-resolved config so the file isn't re-read.

u.RawQuery = ""
u.Fragment = ""

client := &http.Client{Timeout: 10 * time.Second}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium cli/cmd_doctor.go:87

probeExecutorPublicURL creates an http.Client with a nil Transport, so Go falls back to http.DefaultTransport and routes the request through any HTTP_PROXY/HTTPS_PROXY environment variable. On hosts with a corporate proxy configured, tx9 doctor sends the health probe through that proxy instead of directly to the local tailnet URL, causing false failures even when the Executor endpoint is healthy. Consider setting a custom Transport that ignores proxy environment variables (e.g. &http.Transport{Proxy: nil}) so the probe always connects directly.

Suggested change
client := &http.Client{Timeout: 10 * time.Second}
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{Proxy: nil},
}
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/cli/cmd_doctor.go around line 87:

`probeExecutorPublicURL` creates an `http.Client` with a nil `Transport`, so Go falls back to `http.DefaultTransport` and routes the request through any `HTTP_PROXY`/`HTTPS_PROXY` environment variable. On hosts with a corporate proxy configured, `tx9 doctor` sends the health probe through that proxy instead of directly to the local tailnet URL, causing false failures even when the Executor endpoint is healthy. Consider setting a custom `Transport` that ignores proxy environment variables (e.g. `&http.Transport{Proxy: nil}`) so the probe always connects directly.

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.

1 participant