Skip to content

Loadout patch sources that fail to resolve are dropped silently — symlinked sources warn nowhere, missing sources warn below the default log level #1069

Description

@norrietaylor

Fixed with: #1088

Loadout and [session] patch sources that fail to resolve are dropped silently — the session comes up without the file and nothing tells the user.

Two paths, one symptom. Both make docs/guide/agents.md's credential-passthrough story fail open.

1. A literal source that is a symlink is dropped with no message at all

docs/reference/loadouts.md:198-200 scopes symlink handling to glob enumeration:

By default the walker does not follow symlinks while enumerating glob matches; see follow_symlinks and the client config.

A literal (non-glob) source that happens to be a symlink is also dropped, and nothing in the doc predicts it:

$ ln -s /private/tmp/qamin/src.txt /tmp/qamin/link.txt

# loadout: patches = [ { dest = "FROMLINK.txt", source = "/private/tmp/qamin/link.txt" } ]
$ RUST_LOG=warn min … session activate -n symsrc --loadout symsrc --sync none --no-prompt .
Applying loadouts: symsrc
019fae8f-ea40-7fb1-89f4-2f6fc8ce8a44

# inside that session — no file, and no warning was emitted at any log level:
root@symsrc:/workbench# ls -la ~
total 4
drwxr-xr-x  2 root root 4096 Jul 29 15:48 .
drwxr-xr-x 11 root root  280 Jul 29 15:48 ..

Adding follow_symlinks = true to the same loadout makes it land, which pins the cause:

root@symfollow:/workbench# ls -la ~
-rw-r--r-- 1 root root 9 Jul 29 15:48 FROMLINK.txt

Control — the same source as a regular file lands correctly under all three dest shapes, so nothing else about the patch is at fault:

root@desttest:/workbench# find / -name 'REL.txt' -o -name 'TILDE.txt' -o -name 'NESTED.txt'
/home/TILDE.txt
/home/REL.txt
/home/sub/NESTED.txt

2. A missing source does warn, but below the default log level

docs/reference/loadouts.md:187-189:

A source path that does not exist on the host is dropped with a warning at activation rather than failing it

The warning exists but the user does not see it:

$ min … session activate -n missingsrc --loadout missingsrc --sync none --no-prompt .
Applying loadouts: missingsrc
019fae8f-ea49-7383-b7ae-8e4634bdf6b3
                                    <- nothing

$ RUST_LOG=warn min … session activate -n missing2 --loadout missingsrc --sync none --no-prompt .
Applying loadouts: missingsrc
WARN sessions::core::enumerate: patch source not found on host filesystem; dropping
  source_pattern=/private/tmp/qamin/does-not-exist.txt
  walk_root=/private/tmp/qamin/does-not-exist.txt
  missing_path=Some("/private/tmp/qamin/does-not-exist.txt")
019fae90-281f-77b2-a209-2f54d303df46

Applying loadouts: reaches stderr on the same run, so the channel is not the problem — the crate's default filter is.

Why this matters more than the individual cases

docs/guide/agents.md:45-49 and docs/guide/dev-shell.md:58-62 both teach exactly the shape that fails:

patches = [
    { source = "~/.gitconfig", dest = "~/.gitconfig" },
    { source = "~/.ssh",       dest = "~/.ssh" },
]

On this machine ~/.gitconfig is a symlink into a dotfiles repo — the single most common way people keep dotfiles, and loadouts.md:277 explicitly courts that audience ("Turn on when your dotfile tree is a symlink farm (stow, chezmoi)"). The agent starts, has no credentials, and the user gets no signal at the moment it matters.

Suggested fix

  1. Surface every dropped patch source on stderr at activation, alongside Applying loadouts: — both the missing-file case and the symlink case. Failing open is a reasonable default; failing open silently is not.
  2. Emit a warning for the symlink drop, which currently produces none at any level.
  3. Document that follow_symlinks gates literal symlink sources too, not just glob enumeration — or make literal sources follow by default, since a user naming one file explicitly has already expressed the intent a glob walk cannot infer.

Item 1 is the one that matters; 2 and 3 fall out of it.

Not duplicates

gominimal/minimal#196 (leading ~ expansion) and #384 ($VAR expansion) are both closed and concern expanding source patterns, not what happens when an expanded path fails to resolve.

Environment

  • min 0.5.0-rc3.dev.35.gdcdeec68, macOS arm64 (Apple Silicon)
  • Checkout gominimal/minimal main @ c1d466c; installed binary from dcdeec68 (1 commit apart, scripts/-only)
  • All runs against an isolated --minimal-dir/--config-dir. Found during a documentation QA sweep; related epic gominimal/inbox#271.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions