Skip to content

fix: Explain how with-connect works in --help and reject invalid --port - #63

Merged
cgraham-rs merged 2 commits into
mainfrom
cgraham-rs/read-this-userschristophergrah
Oct 14, 2026
Merged

cgraham-rs merged 2 commits into
mainfrom
cgraham-rs/read-this-userschristophergrah

Conversation

@cgraham-rs

@cgraham-rs cgraham-rs commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Agents running with-connect --help only saw a list of flags. They guessed a start subcommand and flags that don't exist, like --json, --name and --data-dir, and their runs failed. See posit-dev/connect#44594.

The help now says what with-connect prints, that the container keeps running, how to stop it, and what -- COMMAND does. The --stop help also says CONTAINER_ID must be exported when no ID is given. Unknown arguments like start or --json still get argparse's standard error.

--port now rejects values outside 1 to 65535 right away. Before, --port 0 was accepted and then failed with a misleading timeout error.

The README eval example did not export the variables, so with-connect --stop could not see CONTAINER_ID in a clean shell. It now exports them.

Fixes #62. Follow-up work is tracked in #61, #64 and #65.

@cgraham-rs
cgraham-rs requested review from a team and tdstein October 8, 2026 19:44
Comment thread main.py Outdated
parser.error(f"unrecognized arguments: {' '.join(unknown)}")
if unknown:
parser.error(
f"unexpected argument '{unknown[0]}', with-connect has no subcommands. "

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new 'no subcommands' error fires for any stray positional value, including word-split values from this repo's own action.yml, so users get a misleading diagnosis.

action.yml builds ARGS="$ARGS -e $line" and expands with-connect $ARGS without quotes. An env input like FOO=bar baz (or a config-file/image path containing a space) becomes -e FOO=bar baz. argparse leaves baz unknown, and the user sees "unexpected argument 'baz', with-connect has no subcommands". The real cause is a quoting/value problem. Before this change the plain "unrecognized arguments: baz" was at least neutral. The same happens with with-connect --license a.lic b.lic. Consider limiting the subcommand hint to known verb-like words (start/run/stop/up/down), or wording it as "unexpected positional argument" without claiming the user tried a subcommand.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the custom error. Unknown arguments now get argparse's standard message, so nothing guesses at a subcommand.

Comment thread main.py Outdated
Comment thread main.py
Comment thread main.py
Comment thread main.py Outdated
Comment thread main.py Outdated
Comment thread test_main.py Outdated
assert "CONTAINER_ID" in result.stdout


def run_cli(*args):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The new run_cli helper isn't reused by the existing subprocess-based tests, and the pure argument-parsing tests spawn a full interpreter when the in-process parse_with_argv helper would do.

test_custom_port, test_stop_argument_in_help, test_stop_nonexistent_container and others still inline the same subprocess.run([sys.executable, 'main.py', ...], capture_output=True, text=True). The four new error-message tests each start a new Python process that imports docker and rsconnect just to exercise argparse. Using parse_with_argv with pytest.raises(SystemExit) and capsys (as the last two new tests already do for the happy path) avoids the process startup cost, and migrating the old call sites to run_cli removes the duplication.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The four subprocess error tests went away with the custom error, along with run_cli. The new port and help tests run in-process. pytest isn't a dependency here and CI runs test_main.py directly, so they catch SystemExit and capture output with contextlib instead of pytest.raises and capsys. Moving the older subprocess tests to a shared helper would be a separate cleanup.

Comment thread main.py Outdated

@tdstein tdstein left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Posted some Claude generated review comments. Overall, I'm wondering if we should be using parser.print_help(); parser.exit() from argparse instead of using a custom error message.

…port right away. Add how to stop the container to --help.
@cgraham-rs cgraham-rs changed the title docs: Make --help explain how with-connect works fix: Explain how with-connect works in --help and reject invalid --port Oct 13, 2026
@cgraham-rs

Copy link
Copy Markdown
Contributor Author

Posted some Claude generated review comments. Overall, I'm wondering if we should be using parser.print_help(); parser.exit() from argparse instead of using a custom error message.

I sampled what other popular CLIs do with a bad argument. Most print a short error instead of the whole help so we followed that simpler implementation here. Happy to discuss further.

Comment thread main.py
Comment on lines +43 to +51
def port_number(value: str) -> int:
"""Parse --port. Docker can only map a fixed host port from 1 to 65535."""
try:
port = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"invalid port value: '{value}'")
if not 1 <= port <= 65535:
raise argparse.ArgumentTypeError(f"{port} is not a valid port. Use a free port from 1 to 65535.")
return port

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

😎

@cgraham-rs

Copy link
Copy Markdown
Contributor Author
$ with-connect --help
usage: with-connect [-h] [--version VERSION] [--license LICENSE] [--image IMAGE] [--config CONFIG] [-e ENV_VARS] [--quiet] [--port PORT]
                    [--stop [CONTAINER_ID]]

Starts Posit Connect in Docker and waits until it is ready.

With no command, prints CONNECT_API_KEY, CONNECT_SERVER and CONTAINER_ID
as KEY=value lines on stdout and leaves the container running.
Stop it later with: with-connect --stop CONTAINER_ID
Progress and errors go to stderr.

With "-- COMMAND", runs COMMAND with CONNECT_API_KEY and CONNECT_SERVER
set, then stops the container and exits with the command's exit code.

options:
  -h, --help            show this help message and exit
  --version VERSION     Posit Connect version (default: release, the latest stable release)
  --license LICENSE     Path to Posit Connect license file (default: ./rstudio-connect.lic)
  --image IMAGE         Container image to use, including tag (use instead of --version)
  --config CONFIG       Path to rstudio-connect.gcfg configuration file
  -e, --env ENV_VARS    Environment variables to pass to Docker container (format: KEY=VALUE)
  --quiet               Suppress progress indicators during image pull
  --port PORT           Port to map the Connect container to (default: 3939)
  --stop [CONTAINER_ID]
                        Stop a running Connect container by ID (uses the exported CONTAINER_ID env var if not specified)

examples:
  with-connect --license ./rstudio-connect.lic
  with-connect --license ./rstudio-connect.lic -- bash -c 'curl -H "Authorization: Key $CONNECT_API_KEY" $CONNECT_SERVER/__api__/v1/content'

@cgraham-rs
cgraham-rs merged commit 105c7a4 into main Oct 14, 2026
5 checks passed
@cgraham-rs
cgraham-rs deleted the cgraham-rs/read-this-userschristophergrah branch October 14, 2026 14:52
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.

--port 0 fails with a misleading 60 second timeout error

2 participants