feat: shell completion for jmp, jmp-admin, and j#422
feat: shell completion for jmp, jmp-admin, and j#422raballew wants to merge 13 commits intojumpstarter-dev:mainfrom
Conversation
❌ Deploy Preview for jumpstarter-docs failed. Why did it fail? →
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a reusable Click completion-command factory, wires shell completions into Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant Entry as j/jmp (entrypoint)
participant Factory as make_completion_command
participant ClickGen as ClickCompletionClass
participant Shell as Shell (bash/zsh/fish)
User->>Entry: run "j|jmp completion <shell>"
Entry->>Factory: invoke completion command
Factory->>ClickGen: get_completion_class(shell) & instantiate
ClickGen-->>Factory: completion source()
Factory-->>Entry: emit completion script (stdout)
Entry-->>User: printed completion script
User->>Shell: source/install emitted script
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py`:
- Around line 58-67: The test currently only ensures no exception is raised but
doesn't check the fallback payload; modify
test_j_shell_complete_returns_empty_on_timeout to capture the result of
run(_j_shell_complete) and assert it equals the expected timeout fallback (e.g.,
an empty list) when env_async (patched as slow_env) sleeps past
_COMPLETION_TIMEOUT_SECONDS; reference the patched symbol
"jumpstarter_cli.j.env_async", the async contextmanager helper "slow_env", the
test runner call "run(_j_shell_complete)", and the timeout constant
"_COMPLETION_TIMEOUT_SECONDS" to locate where to add the assertion.
- Around line 45-55: The test currently only verifies mock_client.cli was
fetched but doesn't ensure the returned CLI callable was actually invoked and
that the SystemExit path ran; update the test for _j_shell_complete to exercise
the SystemExit branch by making mock_client.cli.return_value raise SystemExit(0)
(as you've set on mock_cli_group) and then asserting that the returned callable
was called (e.g. assert mock_client.cli.return_value.assert_called_once()) and
that run(_j_shell_complete) completes without propagating the SystemExit;
reference mock_client.cli, mock_client.cli.return_value (mock_cli_group) and the
_j_shell_complete invocation when adding the assertions.
In `@python/packages/jumpstarter-cli/jumpstarter_cli/j.py`:
- Around line 32-44: The timeout isn't interrupting the thread wait because
to_thread.run_sync is called without abandon_on_cancel; update the call in the
block using anyio.fail_after/_COMPLETION_TIMEOUT_SECONDS (inside the async with
BlockingPortal() and async with env_async(portal, stack) as client) to await
to_thread.run_sync(_run_completion, abandon_on_cancel=True) so the fail_after
timeout can cancel the await immediately; keep the surrounding context (function
_run_completion, client.cli(), BlockingPortal, env_async) unchanged.
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 106-111: The generated completion function _j_completion currently
provides the subcommand list for every cursor position; change its body to only
offer the baked subcommands when the cursor is on the first argument by checking
COMP_CWORD (e.g., if [ "${COMP_CWORD}" -eq 1 ]; then COMPREPLY=($(compgen -W
"..." -- "${COMP_WORDS[COMP_CWORD]}")); fi) and otherwise leave COMPREPLY empty
so default/file completions can run; update the code that builds _j_completion
(the block that uses j_commands, cmds, and lines.append) to emit that COMP_CWORD
guard around the compgen call.
- Around line 183-189: The temp ZDOTDIR assignment in _generate_shell_init
(tmpdir, zshrc_path, env["ZDOTDIR"]) causes zsh to read startup files from the
temp dir and break user profiles; remove the env["ZDOTDIR"] = tmpdir assignment
and instead keep ZDOTDIR unchanged, write init_content to tmpdir/.zshrc, and
ensure the shell loads this file by explicitly sourcing that file when launching
zsh (e.g., prepend a `source {zshrc_path}` invocation to the shell command or
add the temp file to the command invocation) while leaving cmd.extend(["--rcs",
"-o", "inc_append_history", "-o", "share_history"]) as-is so the user’s real
~/.zshenv and friends are still read.
- Around line 155-172: The _launch_fish function currently injects the context
and init_file.name directly into the fish init command (via f'printf
"{context}"; ' and unquoted init_file.name), which permits shell injection;
change _launch_fish to treat context as data by passing it through the
environment (like _launch_bash/_launch_zsh do) and reference it from the fish
function via the environment variable, and avoid interpolating init_file.name
into shell code by using a safely quoted/escaped path or sourcing via a separate
argument; keep using _run_process to launch the shell and add the environment
key (e.g., FISH_PROMPT_CONTEXT) to common_env before calling _run_process.
🪄 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
Run ID: 1e091e91-4dda-463f-b83c-382cf818ca3c
📒 Files selected for processing (11)
python/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/__init__.pypython/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion.pypython/packages/jumpstarter-cli-admin/jumpstarter_cli_admin/completion_test.pypython/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion.pypython/packages/jumpstarter-cli-common/jumpstarter_cli_common/completion_test.pypython/packages/jumpstarter-cli/jumpstarter_cli/completion.pypython/packages/jumpstarter-cli/jumpstarter_cli/j.pypython/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.pypython/packages/jumpstarter-cli/jumpstarter_cli/shell.pypython/packages/jumpstarter/jumpstarter/common/utils.pypython/packages/jumpstarter/jumpstarter/common/utils_test.py
python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (1)
python/packages/jumpstarter/jumpstarter/common/utils.py (1)
174-176: Consider quoting the init file path for defense in depth.While
init_file.nameis system-generated bytempfile.NamedTemporaryFileand unlikely to contain special characters, quoting it provides defense against edge cases (e.g., unusualTMPDIRvalues).♻️ Suggested improvement
if init_file: - init_cmd += f"; source {init_file.name}" + init_cmd += f"; source {shlex.quote(init_file.name)}"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 174 - 176, The init file path used when building init_cmd is not quoted; update the code that builds init_cmd (the block that appends f"; source {init_file.name}") to safely quote the path (e.g., via shlex.quote or equivalent) before interpolation so the call to _run_process([shell, "--init-command", init_cmd], fish_env, lease) gets a properly escaped filename; adjust the code that sets init_cmd and the branch that checks init_file to use the quoted value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 174-176: The init file path used when building init_cmd is not
quoted; update the code that builds init_cmd (the block that appends f"; source
{init_file.name}") to safely quote the path (e.g., via shlex.quote or
equivalent) before interpolation so the call to _run_process([shell,
"--init-command", init_cmd], fish_env, lease) gets a properly escaped filename;
adjust the code that sets init_cmd and the branch that checks init_file to use
the quoted value.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: b5ea477e-d702-40b0-b0e1-a15aac6f105a
📒 Files selected for processing (4)
python/packages/jumpstarter-cli/jumpstarter_cli/j.pypython/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.pypython/packages/jumpstarter/jumpstarter/common/utils.pypython/packages/jumpstarter/jumpstarter/common/utils_test.py
✅ Files skipped from review due to trivial changes (1)
- python/packages/jumpstarter-cli/jumpstarter_cli/j.py
🚧 Files skipped from review as they are similar to previous changes (2)
- python/packages/jumpstarter-cli/jumpstarter_cli/j_completion_test.py
- python/packages/jumpstarter/jumpstarter/common/utils_test.py
|
Something is weird at my side: |
|
this is MacOS / zsh |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
python/packages/jumpstarter/jumpstarter/common/utils.py (2)
159-178: Context injection fixed; minor remaining inconsistency with init file path.The critical shell injection via
contextis now properly fixed using the_JMP_SHELL_CONTEXTenvironment variable (line 160, 168).Line 176 still interpolates
init_file.namedirectly into the fish command. While this is safe in practice (tempfile generates controlled paths like/tmp/tmpXXXXXX.sh), the past review suggested passing it via environment variable for consistency with the context fix.♻️ Optional: Use env var for init file path
def _launch_fish(shell, init_file, common_env, context, lease): - fish_env = common_env | {"_JMP_SHELL_CONTEXT": context} + fish_env = common_env | {"_JMP_SHELL_CONTEXT": context} fish_fn = ( # ... prompt function unchanged ... ) init_cmd = fish_fn if init_file: - init_cmd += f"; source {init_file.name}" + fish_env["_JMP_SHELL_INIT"] = init_file.name + init_cmd += '; source "$_JMP_SHELL_INIT"' return _run_process([shell, "--init-command", init_cmd], fish_env, lease)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 159 - 178, _in _launch_fish the init file path is still interpolated directly via init_file.name into init_cmd; change this to pass the path through the environment like you did for context: add a key (e.g. "_JMP_FISH_INIT") to fish_env containing init_file.name (when init_file is present), and reference that env var in init_cmd instead of init_file.name so _run_process receives no direct string interpolation of the path; update any logic that builds init_cmd and the call to _run_process accordingly.
99-145: Good implementation with minor inconsistency in shell detection.The zsh
compinitinitialization (line 123) correctly addresses the macOS "command not found: compdef" error reported in PR comments. The bashCOMP_CWORDcheck (lines 110-111) properly limits subcommand completion to the first argument position.Minor inconsistency: bash uses
.endswith("bash")(matches bothbashand paths like/usr/bin/bash), while zsh and fish use exact string matches. Sinceshell_nameis derived fromos.path.basename()inlaunch_shell, this works correctly, but the style is inconsistent.♻️ Optional: Consistent shell detection
- if shell_name.endswith("bash"): + if shell_name == "bash":Or alternatively, use
.endswith()for all shells to handle variants likezsh-5.9.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@python/packages/jumpstarter/jumpstarter/common/utils.py` around lines 99 - 145, The shell-detection logic in _generate_shell_init is inconsistent: bash uses shell_name.endswith("bash") but zsh and fish use exact equality; make detection uniform by using endswith for zsh and fish as well (e.g., change comparisons in the branches that currently use shell_name == "zsh" and shell_name == "fish" to shell_name.endswith("zsh") and shell_name.endswith("fish")), so variants like "zsh-5.9" or paths still match consistently with the bash branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 159-178: _in _launch_fish the init file path is still interpolated
directly via init_file.name into init_cmd; change this to pass the path through
the environment like you did for context: add a key (e.g. "_JMP_FISH_INIT") to
fish_env containing init_file.name (when init_file is present), and reference
that env var in init_cmd instead of init_file.name so _run_process receives no
direct string interpolation of the path; update any logic that builds init_cmd
and the call to _run_process accordingly.
- Around line 99-145: The shell-detection logic in _generate_shell_init is
inconsistent: bash uses shell_name.endswith("bash") but zsh and fish use exact
equality; make detection uniform by using endswith for zsh and fish as well
(e.g., change comparisons in the branches that currently use shell_name == "zsh"
and shell_name == "fish" to shell_name.endswith("zsh") and
shell_name.endswith("fish")), so variants like "zsh-5.9" or paths still match
consistently with the bash branch.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: eb6e6b21-e74e-4c24-90a5-9937ef44de36
📒 Files selected for processing (2)
python/packages/jumpstarter/jumpstarter/common/utils.pypython/packages/jumpstarter/jumpstarter/common/utils_test.py
✅ Files skipped from review due to trivial changes (1)
- python/packages/jumpstarter/jumpstarter/common/utils_test.py
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils.py`:
- Around line 119-131: The zsh branch currently appends the source of ~/.zshrc
before running compinit, causing compdef errors; modify the block handling
shell_name.endswith("zsh") so compinit (the "autoload -Uz compinit && compinit"
line) and the eval "$(jmp ...)" / eval "$(jmp-admin ...)" completions are added
to lines before the optional '[ -f ~/.zshrc ] && source ~/.zshrc' when
use_profiles is True, preserving the j_commands logic (compdef vs eval "$(j
...)" behavior) and returning the joined lines as before.
- Around line 126-128: The zsh `_arguments` invocation built in utils.py when
j_commands is present uses wrong syntax; update the string produced by the
lines.append call (the one currently building compdef with f"compdef '_arguments
\"1:(({cmds}))\"' j") to include the required message field and single
parentheses around the value list (for example use a message like "subcommand"
and value list "(cmd1 cmd2 ...)"); ensure the f-string around variable
j_commands (cmds) produces `_arguments '1:subcommand:(<cmds>)'` and that quoting
is correct so the final compdef call registers properly.
🪄 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
Run ID: 62822921-b23b-460d-a58d-23b050ead306
📒 Files selected for processing (2)
python/packages/jumpstarter/jumpstarter/common/utils.pypython/packages/jumpstarter/jumpstarter/common/utils_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
- python/packages/jumpstarter/jumpstarter/common/utils_test.py
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@python/packages/jumpstarter/jumpstarter/common/utils_test.py`:
- Around line 292-300: The zsh test invoking subprocess.run with ["zsh",
"--rcs", "-c", "exit 0"] doesn't source init files in non-interactive mode and
must be changed to spawn zsh in interactive or login mode (e.g., use "-i" or
"-l") so the completion init logic runs; update the subprocess.run call(s) that
reference zsh to use an interactive/login flag or otherwise explicitly source
the tmpdir ~/.zshrc within the invoked command. Also update the bash test that
uses ["bash", "--rcfile", ... , "-c", ...] to instead explicitly source the
rcfile in the command (modify the subprocess.run invocation referencing
bash/--rcfile) so the bash initialization is actually executed during the test.
🪄 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
Run ID: 255e6b21-8a63-477a-a595-4818689ba271
📒 Files selected for processing (2)
.github/workflows/python-tests.yamlpython/packages/jumpstarter/jumpstarter/common/utils_test.py
54ba220 to
9774aa7
Compare
I found 2 problems:
For jmp it seems to work well. |
|
@ambient-code why is CI failing? |
7f73dd8 to
b6b7bae
Compare
|
Now completion for bare jmp does not work, but the prompt is back |
|
Extract a shared make_completion_command factory into jumpstarter-cli-common and use it in both jmp and jmp-admin CLIs. This adds a `completion` subcommand to jmp-admin (and by extension `jmp admin`) supporting bash, zsh, and fish. Generated-By: Forge/20260407_145514_2280530_d26e63ff Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The shared make_completion_command factory lacked direct unit tests in its own package, relying only on indirect coverage from downstream packages. This adds tests exercising the factory with a minimal Click group for all shell types and error cases. Generated-By: Forge/20260407_145514_2280530_d26e63ff Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Auto-source completions for jmp, jmp-admin, and j when entering jmp shell
(bash, zsh, fish). For j, command names are baked into the shell init script
at startup to avoid slow gRPC calls on every TAB press.
Adds `j completion {bash,zsh,fish}` subcommand for standalone use, with a
fast-path dispatch that avoids the full async stack. Catches SystemExit from
Click's completion handler before it propagates through anyio task groups.
Closes jumpstarter-dev#35
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix zsh temp file leak by cleaning up .zshrc in _launch_zsh finally block (F001) - Validate j_commands against safe pattern to prevent shell injection (F002) - Refactor launch_shell into per-shell helpers to reduce complexity (F003) - Add debug logging when j command extraction fails (F004) - Add test for zsh temp file cleanup in launch_shell (F005) - Add 5-second timeout to _j_shell_complete to prevent shell freezes (F006) - Rename admin completion tests to describe expected behavior (F007) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add None guard for get_completion_class return value (F001) - Simplify zsh temp file handling with mkdtemp, eliminating unnecessary intermediate file (F002) - Quote fish completion argument for defense-in-depth (F003) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix shell injection in fish prompt by passing context via env var - Add COMP_CWORD guard to limit bash completion to first argument - Restore original ZDOTDIR in generated zshrc to preserve user config - Add abandon_on_cancel=True to completion thread for clean timeout - Strengthen test assertions for completion and timeout behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The zsh init script used compdef without ensuring compinit had been called. On macOS zsh where compinit is not loaded by /etc/zshrc, this caused "command not found: compdef" errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When use_profiles=True, the user's ~/.zshrc (or ~/.bashrc) is sourced inside the init script, which typically sets PROMPT/PS1 and overrides the custom jumpstarter prompt set via environment variable. Fix by appending the prompt assignment at the end of the init script so it runs after user profile sourcing. Pass context via _JMP_SHELL_CONTEXT env var (consistent with fish) instead of interpolating it directly. Also refactor _launch_bash to manage its own init file lifecycle, matching the pattern used by _launch_zsh. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
b6b7bae to
2529afe
Compare
When the user's shell profile modifies PATH (common on macOS where /etc/zshenv runs path_helper, or with oh-my-zsh), the bare command names jmp/jmp-admin/j may no longer be found by the time the completion eval commands run. Resolve the CLI binary paths at init generation time using shutil.which() so the completion scripts reference absolute paths that survive PATH modifications. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The test relied on jmp being installed on PATH but CI environments do not have it, causing shutil.which to return None and the test to fail with bare command names instead of absolute paths. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
jmp,jmp-admin, andjwhen enteringjmp shell(bash, zsh, fish)jsubcommand names into shell init scripts at startup for instant TAB completion (no gRPC on every keypress)j completion {bash,zsh,fish}subcommand with fast-path dispatch that avoids the full async stack and catchesSystemExitcleanlymake_completion_commandfactory injumpstarter-cli-commonreused across all three CLIsCloses #35
Test plan
_generate_shell_initcovering bash/zsh/fish with and without j_commands, profiles, and unknown shellsj completioncovering bash/zsh/fish script generation, error cases, and SystemExit handlingjmp shellthen TAB-completejmp,jmp-admin, andjsubcommands in bash/zsh/fishj completion bash | source /dev/stdinoutside jmp shell🤖 Generated with Claude Code