Skip to content

Add dl <workspace> dotfiles refresh subcommand - #44

Merged
blooop merged 2 commits into
mainfrom
add-dotfiles-refresh-command
Aug 14, 2026
Merged

blooop merged 2 commits into
mainfrom
add-dotfiles-refresh-command

Conversation

@blooop

@blooop blooop commented Apr 6, 2026

Copy link
Copy Markdown
Owner

Summary

Adds dl <workspace> dotfiles, a subcommand that refreshes dotfiles inside an already-provisioned workspace.

  • dotfiles_update() runs chezmoi update --force && pixi global sync over one devpod ssh, falling back to cloning DOTFILES_URL and running install.sh when chezmoi is not on the image.
  • dl <ws> dotfiles starts the workspace first if it is not Running, then refreshes.
  • Bash completion, --help, and the README command table list the new subcommand.

Motivation

devpod applies dotfiles when it provisions a workspace. Main already covers that half: workspace_up() reads DOTFILES_URL/DOTFILES_SCRIPT from the devpod context and passes --dotfiles/--dotfiles-script to devpod up (devlaunch/dl.py:1934-1937).

What is missing is refreshing dotfiles in a workspace that is already up. Attaching to a Running workspace deliberately skips devpod up altogether — test/test_devpod_spawn_counts.py::test_attaching_to_a_running_workspace pins that path to one status and two ssh calls, with no up — so a long-lived workspace keeps whatever dotfiles it was born with. This subcommand is the explicit way to refresh them without a restart or recreate.

Scope change on rebase

This branch originally carried a second feature: an automatic dotfiles refresh on every attach. That half has been dropped. The reasoning is in a comment on this PR. Short version: its insertion points no longer exist (main collapsed them into attach_workspace()), and re-adding the hook fails 5 tests in test/test_devpod_spawn_counts.py, the suite that exists to catch exactly this kind of added round-trip. The idea is not rejected, only descoped — see the follow-up issue for what an opt-in version would have to satisfy.

Test plan

  • pixi run test — 1320 passed, 23 deselected
  • pixi run lint — ruff and ty clean, pylint 10.00/10
  • pixi run prek — all hooks pass
  • pixi run dl --help lists dl <user/repo> dotfiles

New tests cover the chezmoi path, the fallback path, shell-quoting of DOTFILES_URL, the missing-DOTFILES_URL case, and the subcommand against Running, Stopped, and failed-start workspaces.

@sourcery-ai

sourcery-ai Bot commented Apr 6, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a new dotfiles_update workflow that can be run explicitly via dl <workspace> dotfiles and is also invoked automatically on workspace attach, ensuring dotfiles are refreshed via chezmoi update/pixi global sync with a git-based fallback, and updates tests, bash completions, help text, and README accordingly.

Sequence diagram for workspace attach with dotfiles auto-refresh

sequenceDiagram
    actor Developer
    participant dl_cli as dl
    participant DevPod
    participant Workspace as Workspace_container

    Developer->>dl_cli: dl <workspace>
    activate dl_cli
    dl_cli->>DevPod: get_workspace_state(workspace_id)
    DevPod-->>dl_cli: state

    alt Fast attach (already Running)
        dl_cli->>Workspace: dotfiles_update(workspace_id)
        activate Workspace
        Workspace-->>dl_cli: exit code (df_ret)
        deactivate Workspace
        alt df_ret != 0
            dl_cli->>dl_cli: log warning Dotfiles update failed
        end
        dl_cli->>Workspace: workspace_ssh(workspace_id)
        Workspace-->>dl_cli: SSH session
    else Cold start or not Running
        dl_cli->>DevPod: workspace_up(workspace_spec)
        DevPod-->>dl_cli: result
        dl_cli->>Workspace: dotfiles_update(workspace_id)
        activate Workspace
        Workspace-->>dl_cli: exit code (df_ret)
        deactivate Workspace
        alt df_ret != 0
            dl_cli->>dl_cli: log warning Dotfiles update failed
        end
        dl_cli->>Workspace: workspace_ssh(workspace_id)
        Workspace-->>dl_cli: SSH session
    end

    dl_cli-->>Developer: Attached shell in workspace
    deactivate dl_cli
Loading

Flow diagram for dotfiles_update workspace refresh logic

flowchart TD
    A[dotfiles_update workspace_id] --> B[get_context_options]
    B --> C[Read DOTFILES_URL from context]
    C --> D{DOTFILES_URL set?}

    D -- Yes --> E[Construct fallback: git clone DOTFILES_URL
into temp dir, run install.sh, rm -rf temp]
    D -- No --> F[Construct fallback: echo chezmoi not found and no DOTFILES_URL, exit 1]

    E --> G[Build update_cmd
if command -v chezmoi
  run chezmoi update --force
  run pixi global sync
else run fallback]
    F --> G

    G --> H[Call workspace_ssh workspace_id, command=update_cmd]
    H --> I[Return SSH exit code]
Loading

File-Level Changes

Change Details Files
Introduce dotfiles_update helper to refresh dotfiles inside a workspace via SSH, with chezmoi/pixi primary path and git-based fallback.
  • Add dotfiles_update(workspace_id) that builds a shell command to run chezmoi update --force and pixi global sync inside the workspace via workspace_ssh.
  • Use DOTFILES_URL from get_context_options() to construct a fallback path that clones the dotfiles repo to a temp dir, runs install.sh, and cleans up, or exits with an error if DOTFILES_URL is missing.
  • Emit user-facing echo messages in the update/fallback command string and propagate the workspace_ssh return code.
devlaunch/dl.py
Wire a new dotfiles subcommand into the CLI to manually refresh dotfiles for a workspace, ensuring the workspace is running first.
  • Extend print_help() text to document dl <user/repo> dotfiles as a workspace subcommand.
  • In main(), handle subcommand == 'dotfiles' by checking workspace state, starting the workspace via workspace_up if not Running, and then calling dotfiles_update(workspace_id), returning its exit code.
  • Ensure workspace_up failures for the dotfiles subcommand are propagated as the main() return code.
devlaunch/dl.py
README.md
Automatically refresh dotfiles on workspace attach for both fast-attach and standard attach paths, while treating update failures as non-fatal.
  • In the fast-attach path (existing Running workspace with no custom_id), call dotfiles_update(workspace_id) before workspace_ssh; if it fails, log a warning and continue attaching.
  • In the standard attach path after workspace_up completes and hostname is set, call dotfiles_update(workspace_id) before workspace_ssh; on failure, log a warning and continue.
  • Keep the rest of the attach behavior unchanged aside from the pre-attach dotfiles refresh.
devlaunch/dl.py
Update bash completions, help text, README, and tests to cover the new dotfiles behavior and subcommand.
  • Add dotfiles to the workspace subcommands list in the bash completion script so it’s offered as a completion option.
  • Document the new dl <user/repo> dotfiles subcommand in the README’s commands table.
  • Patch main() call sites in tests to inject a dotfiles_update mock where auto-update is now called, ensuring existing behavior is validated with the new call in place.
  • Add TestDotfilesUpdate to verify the SSH command built by dotfiles_update, including chezmoi/pixi usage and DOTFILES_URL fallback behaviors.
  • Add TestMainDotfilesSubcommand to verify dotfiles subcommand semantics for running/stopped workspaces and error propagation from workspace_up.
devlaunch/completions/dl.bash
README.md
test/test_dl.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codecov

codecov Bot commented Apr 6, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.70%. Comparing base (856e1b8) to head (3e4f093).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #44      +/-   ##
==========================================
+ Coverage   94.67%   94.70%   +0.03%     
==========================================
  Files          21       21              
  Lines        2591     2607      +16     
==========================================
+ Hits         2453     2469      +16     
  Misses        138      138              
Files with missing lines Coverage Δ
devlaunch/dl.py 93.56% <100.00%> (+0.10%) ⬆️

Impacted file tree graph

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

@sourcery-ai sourcery-ai 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.

Hey - I've found 3 issues, and left some high level feedback:

  • In dotfiles_update, dotfiles_url is interpolated directly into the git clone command without shell quoting, so consider wrapping it in quotes or otherwise sanitizing to avoid shell injection or breakage with unusual URLs.
  • The dotfiles auto-refresh logic in main() is duplicated in both attach paths; consider extracting a small helper to perform dotfiles_update and warning/logging so behavior stays consistent if this flow changes later.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `dotfiles_update`, `dotfiles_url` is interpolated directly into the `git clone` command without shell quoting, so consider wrapping it in quotes or otherwise sanitizing to avoid shell injection or breakage with unusual URLs.
- The dotfiles auto-refresh logic in `main()` is duplicated in both attach paths; consider extracting a small helper to perform `dotfiles_update` and warning/logging so behavior stays consistent if this flow changes later.

## Individual Comments

### Comment 1
<location path="devlaunch/dl.py" line_range="808" />
<code_context>
+        fallback = (
+            f'echo "chezmoi not found, running full install..." && '
+            f"DOTFILES_DIR=$(mktemp -d) && "
+            f"git clone {dotfiles_url} \"$DOTFILES_DIR\" && "
+            f'cd "$DOTFILES_DIR" && bash install.sh && '
+            f'rm -rf "$DOTFILES_DIR" && '
</code_context>
<issue_to_address>
**🚨 issue (security):** Interpolate `dotfiles_url` more safely to avoid shell injection and quoting issues.

`dotfiles_url` is inserted directly into a single-string shell command. If `DOTFILES_URL` contains spaces or shell metacharacters (e.g., from misconfiguration), the command can break or allow command injection. Please either properly quote the URL (e.g., `
</issue_to_address>

### Comment 2
<location path="devlaunch/dl.py" line_range="895" />
<code_context>
     dl <user/repo> restart           Stop and start (no rebuild)
     dl <user/repo> recreate          Recreate container
     dl <user/repo> reset             Clean slate (remove all, recreate)
+    dl <user/repo> dotfiles         Refresh dotfiles (chezmoi update)
     dl <user/repo> -- <command>      Run shell command in workspace

</code_context>
<issue_to_address>
**nitpick:** Help text no longer matches the actual behavior of `dotfiles`.

`dotfiles_update` also runs `pixi global sync` and may clone/run `install.sh` when `chezmoi` is unavailable, so the help text understates what happens. Please update the wording to reflect these additional steps while staying concise.
</issue_to_address>

### Comment 3
<location path="devlaunch/dl.py" line_range="795" />
<code_context>
     return result.returncode


+def dotfiles_update(workspace_id: str) -> int:
+    """Refresh dotfiles inside a running workspace.
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider restructuring the dotfiles update command into composable parts and extracting the repeated update-and-warn logic into a helper to simplify and centralize behavior.

You can reduce complexity without changing behavior by:

1. **Structuring the shell command construction**
2. **Deduplicating the “update + warn” pattern**

### 1. Make `dotfiles_update` easier to read/extend

Instead of one long interpolated string, build the command from small, named fragments and then join them. This keeps all behavior but lowers cognitive load:

```python
def dotfiles_update(workspace_id: str) -> int:
    """Refresh dotfiles inside a running workspace."""
    ctx = get_context_options()
    dotfiles_url = ctx.get("DOTFILES_URL", "")

    if dotfiles_url:
        fallback_parts = [
            'echo "chezmoi not found, running full install..."',
            'DOTFILES_DIR=$(mktemp -d)',
            f"git clone {dotfiles_url} \"$DOTFILES_DIR\"",
            'cd "$DOTFILES_DIR"',
            "bash install.sh",
            'rm -rf "$DOTFILES_DIR"',
            'echo "Dotfiles installed successfully"',
        ]
    else:
        fallback_parts = [
            'echo "chezmoi not found and no DOTFILES_URL configured"',
            "exit 1",
        ]

    chezmoi_parts = [
        'echo "Updating dotfiles..."',
        "chezmoi update --force",
        'echo "Syncing pixi global packages..."',
        "pixi global sync",
        'echo "Dotfiles updated successfully"',
    ]

    fallback_cmd = " && ".join(fallback_parts)
    chezmoi_cmd = " && ".join(chezmoi_parts)

    update_cmd = (
        "if command -v chezmoi >/dev/null 2>&1; then "
        f"{chezmoi_cmd}; "
        f"else {fallback_cmd}; "
        "fi"
    )

    return workspace_ssh(workspace_id, command=update_cmd)
```

Behavior is identical, but it’s now obvious where to add/remove steps.

### 2. Deduplicate “update dotfiles if possible” in `main()`

The “call `dotfiles_update`, check return, log warning, continue” pattern appears twice. A tiny helper keeps this in one place:

```python
def refresh_dotfiles_if_possible(workspace_id: str) -> None:
    ret = dotfiles_update(workspace_id)
    if ret != 0:
        logging.warning("Dotfiles update failed, continuing anyway...")
```

Then in `main()`:

```python
# Fast-attach: skip workspace_up() if workspace is already running
if custom_id is None and get_workspace_state(workspace_id) == "Running":
    logging.info(f"Workspace {workspace_id} is already running, attaching...")
    setup_hostname(workspace_id)
    refresh_dotfiles_if_possible(workspace_id)
    ret = workspace_ssh(
        workspace_id,
        shell_command,
        workdir=get_container_workdir(workspace_id),
    )
    update_cache_background()
    return ret

# ...

# After ensuring workspace is up
setup_hostname(workspace_id)
refresh_dotfiles_if_possible(workspace_id)

# Attach to workspace
ret = workspace_ssh(
    workspace_id,
    shell_command,
    workdir=get_container_workdir(workspace_id),
)
```

This keeps all functionality intact while addressing the complexity concern and making future changes to the dotfiles refresh behavior centralized.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread devlaunch/dl.py Outdated
Comment thread devlaunch/dl.py Outdated
dl <user/repo> restart Stop and start (no rebuild)
dl <user/repo> recreate Recreate container
dl <user/repo> reset Clean slate (remove all, recreate)
dl <user/repo> dotfiles Refresh dotfiles (chezmoi update)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

nitpick: Help text no longer matches the actual behavior of dotfiles.

dotfiles_update also runs pixi global sync and may clone/run install.sh when chezmoi is unavailable, so the help text understates what happens. Please update the wording to reflect these additional steps while staying concise.

Comment thread devlaunch/dl.py
return result.returncode


def dotfiles_update(workspace_id: str) -> int:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider restructuring the dotfiles update command into composable parts and extracting the repeated update-and-warn logic into a helper to simplify and centralize behavior.

You can reduce complexity without changing behavior by:

  1. Structuring the shell command construction
  2. Deduplicating the “update + warn” pattern

1. Make dotfiles_update easier to read/extend

Instead of one long interpolated string, build the command from small, named fragments and then join them. This keeps all behavior but lowers cognitive load:

def dotfiles_update(workspace_id: str) -> int:
    """Refresh dotfiles inside a running workspace."""
    ctx = get_context_options()
    dotfiles_url = ctx.get("DOTFILES_URL", "")

    if dotfiles_url:
        fallback_parts = [
            'echo "chezmoi not found, running full install..."',
            'DOTFILES_DIR=$(mktemp -d)',
            f"git clone {dotfiles_url} \"$DOTFILES_DIR\"",
            'cd "$DOTFILES_DIR"',
            "bash install.sh",
            'rm -rf "$DOTFILES_DIR"',
            'echo "Dotfiles installed successfully"',
        ]
    else:
        fallback_parts = [
            'echo "chezmoi not found and no DOTFILES_URL configured"',
            "exit 1",
        ]

    chezmoi_parts = [
        'echo "Updating dotfiles..."',
        "chezmoi update --force",
        'echo "Syncing pixi global packages..."',
        "pixi global sync",
        'echo "Dotfiles updated successfully"',
    ]

    fallback_cmd = " && ".join(fallback_parts)
    chezmoi_cmd = " && ".join(chezmoi_parts)

    update_cmd = (
        "if command -v chezmoi >/dev/null 2>&1; then "
        f"{chezmoi_cmd}; "
        f"else {fallback_cmd}; "
        "fi"
    )

    return workspace_ssh(workspace_id, command=update_cmd)

Behavior is identical, but it’s now obvious where to add/remove steps.

2. Deduplicate “update dotfiles if possible” in main()

The “call dotfiles_update, check return, log warning, continue” pattern appears twice. A tiny helper keeps this in one place:

def refresh_dotfiles_if_possible(workspace_id: str) -> None:
    ret = dotfiles_update(workspace_id)
    if ret != 0:
        logging.warning("Dotfiles update failed, continuing anyway...")

Then in main():

# Fast-attach: skip workspace_up() if workspace is already running
if custom_id is None and get_workspace_state(workspace_id) == "Running":
    logging.info(f"Workspace {workspace_id} is already running, attaching...")
    setup_hostname(workspace_id)
    refresh_dotfiles_if_possible(workspace_id)
    ret = workspace_ssh(
        workspace_id,
        shell_command,
        workdir=get_container_workdir(workspace_id),
    )
    update_cache_background()
    return ret

# ...

# After ensuring workspace is up
setup_hostname(workspace_id)
refresh_dotfiles_if_possible(workspace_id)

# Attach to workspace
ret = workspace_ssh(
    workspace_id,
    shell_command,
    workdir=get_container_workdir(workspace_id),
)

This keeps all functionality intact while addressing the complexity concern and making future changes to the dotfiles refresh behavior centralized.

@blooop

blooop commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

Leaving this open — unlike #32, #35 and #36, this one is not superseded. Main has no dotfiles subcommand:

$ grep -n 'dotfiles_update\|== "dotfiles"' devlaunch/dl.py
(no matches)
$ grep -n 'ws_cmds=' devlaunch/completions/dl.bash
64:    local ws_cmds="up stop rm code restart recreate reset --"

So dl <ws> dotfiles — chezmoi update with a clone-and-install.sh fallback — is real work that has not landed anywhere.

What is blocking it is age, not correctness. The branch is 203 commits behind main and conflicts, and its CI predates the gate job that is now a required check, so gate never appears and the PR cannot satisfy the ruleset however green the rest looks.

To land it, it needs a rebase onto current main. Two things changed underneath it that the rebase has to account for:

  • get_context_options() now exists on main (dl.py:1806-1845) with a disk cache and a TTL keyed on devpod's config file. This PR calls it, so the call is fine — but the caching means a DOTFILES_URL set moments earlier may not be visible yet, which is worth a thought for a command whose whole job is "refresh now".
  • --dotfiles/--dotfiles-script are already passed to devpod up (dl.py:1932-1937), so the provisioning half is done and this PR is now only about the refresh-in-place half. The description should probably say so.

Not doing that rebase here, since it is feature work rather than a CI fix.

@blooop
blooop force-pushed the add-dotfiles-refresh-command branch from 5f4648f to 960163f Compare August 14, 2026 06:37
@blooop blooop changed the title Add dotfiles refresh command and auto-update on attach Add dl <workspace> dotfiles refresh subcommand Aug 14, 2026
@blooop

blooop commented Aug 14, 2026

Copy link
Copy Markdown
Owner Author

Rebased onto current main (was ~207 commits behind and conflicting). The branch now carries only the dl <ws> dotfiles subcommand. The auto-update-on-attach half has been dropped. Title and description updated to match.

This is a scope reduction, not a rejection of the idea — the follow-up issue tracks what an opt-in version would need.

Why the auto-attach half came out

Its insertion points no longer exist. It patched two call sites that each did setup_hostname(...) then workspace_ssh(...). Main has since collapsed both into attach_workspace() (devlaunch/dl.py:2120). That one refactor accounts for 2 of 2 conflicts in dl.py and 13 of 14 in test_dl.py — the test conflicts are all the same mechanical churn, an added @patch("devlaunch.dl.dotfiles_update") fixture colliding with main's independent rename of the parameter next to it. Landing the hook now means choosing afresh where it goes inside attach_workspace(), which is a rewrite rather than a rebase. The subcommand half, by contrast, rebased with zero conflicts.

It breaks the round-trip budget. I injected the hook into attach_workspace() to check, and it fails 5 tests in test/test_devpod_spawn_counts.py:

FAILED TestHotCommandSpawnCounts::test_attaching_to_a_running_workspace
FAILED TestHotCommandSpawnCounts::test_a_one_shot_command_skips_the_hostname_round_trip
FAILED TestHotCommandSpawnCounts::test_a_git_spec_one_shot_on_a_running_workspace
FAILED TestAttachHelper::test_an_interactive_attach_sets_the_hostname
FAILED TestAttachHelper::test_a_one_shot_command_does_not

That module's docstring says it exists so "a change that reintroduces a redundant round-trip fails here instead of quietly costing half a second". The failures are the suite doing its job, not incidental breakage.

The cost is real. #139 measured a devpod ssh --command trip at ~1.73s, roughly 99% connection setup rather than work. The hook adds one such trip to every attach before chezmoi update (a network git pull) and pixi global sync even begin. attach_workspace()'s own docstring shows how carefully main now budgets this — it skips the much cheaper hostname round-trip for one-shot commands because "the round-trip would buy that command nothing."

The two specific problems to solve before it returns

  • It fires on every attach, including one-shot dl <ws> -- cmd. That is the path Launch latency: dl <spec> -- <cmd> to a running command #139 is about and the shape wayfinder uses for every agent launch, where nothing renders a prompt and a dotfiles refresh buys the command nothing.
  • An unreachable dotfiles remote hangs rather than failing fast. A non-zero exit is handled — it logs a warning and continues — but there is no timeout anywhere in the path, so chezmoi update or git clone blocks on a network timeout or a credential prompt while the user waits to get into their shell.

What is on the branch now

960163f. pixi run test 1320 passed, pixi run lint clean with pylint 10.00/10, pixi run prek all hooks passing. Two fixes on top of the original work: DOTFILES_URL is now shlex.quoted before interpolation into the clone command (it comes from local devpod config so the risk is low, but a URL containing a space would otherwise split into two git clone arguments), and the completion file was rebased onto main's version so it keeps the up command and the --devcontainer block it had drifted away from.

blooop added 2 commits August 14, 2026 07:40
devpod applies dotfiles when it provisions a workspace, and workspace_up
already passes --dotfiles/--dotfiles-script to `devpod up`. But attaching
to a workspace that is already Running skips `devpod up` entirely, so a
long-lived workspace keeps whatever dotfiles it was created with until it
is restarted or recreated.

This adds an explicit way to refresh them in place:

- `dl <workspace> dotfiles` subcommand, which starts the workspace first
  if it is not already running
- dotfiles_update() runs `chezmoi update --force && pixi global sync` over
  one devpod ssh, falling back to cloning DOTFILES_URL and running
  install.sh when chezmoi is not on the image
- Bash completion, help text, and README updated
The fallback path interpolates DOTFILES_URL straight into a shell command
for `git clone`. It comes from local devpod config rather than anywhere
hostile, so this is not a security fix, but a URL containing a space split
into two arguments and failed with a message about the wrong thing. Quote
it with shlex.quote, and cover it with a test.

Also normalises string quoting and line length in the surrounding block to
what ruff format emits.
@blooop
blooop force-pushed the add-dotfiles-refresh-command branch from 960163f to 3e4f093 Compare August 14, 2026 06:40
@blooop
blooop merged commit dcdb077 into main Aug 14, 2026
12 checks passed
@blooop
blooop deleted the add-dotfiles-refresh-command branch August 14, 2026 06:51
@blooop blooop mentioned this pull request Aug 14, 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.

1 participant