Skip to content

ext: mount path sources over a non-empty includes dir, and say where a path resolved - #236

Open
mobileoverlord wants to merge 2 commits into
mainfrom
jschneck/ext-path-mount-and-errors
Open

ext: mount path sources over a non-empty includes dir, and say where a path resolved#236
mobileoverlord wants to merge 2 commits into
mainfrom
jschneck/ext-path-mount-and-errors

Conversation

@mobileoverlord

Copy link
Copy Markdown
Contributor

What

Two failures make source: { type: path } hard to land, in sequence.

1. The path error did not say what the path was relative to. A relative path: resolves against src_dir, else the config file's own directory — neither is visible from the config. The old message echoed the joined path (frequently still relative, when the config was given as a bare avocado.yaml) and identified the base only as the words "config directory":

Extension source path does not exist: extensions/avocado-conn
Path was resolved relative to: config directory

2. Then bindfs refused to mount. With the path finally correct, flipping an extension from a package or git source leaves that fetch's tree at includes/<ext>, and bindfs on libfuse2 rejects a non-empty mountpoint:

fuse: mountpoint is not empty
fuse: if you are sure this is safe, use the 'nonempty' mount option
[ERROR] Failed to install SDK dependencies: Failed to initialize SDK environment.

avocado clean was the only way past it. The code comment there assumed the opposite ("stale package-sourced files are harmless — bindfs overlays them"), which holds only on libfuse3.

How

The error now reports the source as written, the absolute resolved path, the absolute base it resolved against, and the config that declared it — and suggests the directory when it exists at <base>/<name> or <base>/extensions/<name>, which is the common wrong-prefix miss:

Failed to fetch extension 'my-ext': source path 'extensions/foo' does not exist
  resolved to: /work/proj/extensions/foo
  relative to: /work/proj
  declared in: /work/proj/avocado.yaml
  did you mean: /work/proj/foo

ext fetch and derive_ext_path_mounts share that text, so the pre-flight error and the mount-time warning agree.

For the mount, -o nonempty lets the path source shadow whatever the earlier fetch left underneath — which is what a path source means. libfuse3 removed the option and permits non-empty mountpoints by default, so a plain retry follows, and a mount that still fails names the extension and its paths instead of exiting on a bare fuse error.

The mount block was duplicated verbatim between the local and remote entrypoint scripts; it is one snippet const now (following REPO_TLS_SETUP_SNIPPET), so the fix applies to both.

Results

Verified in avocadolinux/sdk:2026 (bindfs 1.18.4 / libfuse 2.9.9) that the previous mount fails on a populated includes/<ext> and the new one succeeds, with the host tree shadowing the stale files. -o nonempty is also accepted by the 2024 SDK's bindfs 1.17.7.

New tests: both entrypoint scripts carry the tolerant mount and parse under bash -n; the message names its base and suggests the real directory, and suggests nothing when no candidate exists. Full suite green, cargo fmt --check and cargo clippy --all-targets clean.

…a path resolved

Two things made `source: { type: path }` painful to get working.

A relative `path:` resolves against `src_dir`, else the config file's own
directory — neither visible from the config. The error echoed the joined path
(often still relative) and named the base only as "config directory". It now
reports the source as written, the absolute resolved path, the absolute base it
was resolved against, and the config it came from; when the directory exists at
`<base>/<name>` or `<base>/extensions/<name>` it points there, which covers a
wrong prefix. `ext fetch` and the container's mount derivation share the text.

Once the path was right, bindfs refused to mount: flipping an extension from a
package or git source leaves that fetch's tree at `includes/<ext>`, and bindfs
on libfuse2 rejects a non-empty mountpoint ("fuse: mountpoint is not empty"),
leaving `avocado clean` as the only way out. `-o nonempty` lets the path source
shadow what is underneath, which is what a path source means; libfuse3 dropped
the option and allows it by default, so a plain retry covers that, and a failed
mount now names the extension instead of exiting on a bare fuse error.

The mount block was duplicated verbatim between the local and remote entrypoint
scripts; it is one snippet const now, so the fix lands in both.
Copilot AI lite review requested due to automatic review settings September 3, 2026 18:26

Copilot AI 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.

🟡 Changes recommended

There are a few verified user-facing documentation/message accuracy issues in the new mount snippet and mount-time warning path that should be corrected before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves the UX and reliability of source: { type: path } extensions by (1) making missing-path errors explicitly state what base a relative path was resolved against and (2) making bindfs mounts succeed even when includes/<ext> is non-empty (common after switching an extension from package/git to path), while deduplicating the mount logic across entrypoint scripts.

Changes:

  • Centralizes and reuses a shared bindfs mount snippet that tolerates non-empty mountpoints (libfuse2 via -o nonempty, with retry for libfuse3).
  • Introduces a shared missing_path_source_message helper that reports the declared path, resolved absolute path, base directory, config location, and a “did you mean” hint.
  • Updates container mount derivation warnings to reuse the same missing-path messaging used by ext fetch.
File summaries
File Description
src/utils/ext_fetch.rs Improves type: path validation and introduces a shared missing-path message helper with tests.
src/utils/container.rs Deduplicates entrypoint mount logic into a shared snippet, makes mounts tolerant of non-empty targets, and aligns mount-time warnings with ext fetch messaging.
Review details

Suppressed comments (1)

src/utils/container.rs:484

  • The EXT_PATH_MOUNT_SNIPPET header comments mention $AVOCADO_PREFIX and claim it must be set, but the snippet actually mounts to /opt/_avocado/${AVOCADO_TARGET}/includes and is injected before AVOCADO_PREFIX is exported in the entrypoint scripts. This can mislead future edits/debugging of mount paths.
# Mount extension source paths with bindfs (for path-based extensions)
# These are mounted at /mnt/ext/<ext_name> and need to be bindfs'd to $AVOCADO_PREFIX/includes/<ext_name>
if [ -n "$AVOCADO_EXT_PATH_MOUNTS" ]; then
    # AVOCADO_PREFIX must be set before this - use the target from environment
    EXT_PREFIX="/opt/_avocado/${AVOCADO_TARGET}/includes"
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/utils/container.rs
Comment thread src/utils/container.rs Outdated
Comment thread src/utils/container.rs Outdated
Comment thread src/utils/container.rs Outdated
Comment thread src/utils/container.rs
lee-reinhardt
lee-reinhardt previously approved these changes Sep 3, 2026
…elog

The snippet's doc comment named $AVOCADO_PREFIX/includes/<ext>, but the prefix
is exported further down the entrypoint scripts and the snippet builds the path
from AVOCADO_TARGET; the comment now says so.

`!resolved.is_dir()` is also false for a file, so the message decides between
"does not exist" and "is not a directory" by looking, rather than sending the
reader after a path that is right there.

Both bindfs attempts' stderr is captured and printed only if the mount fails:
whichever attempt is informative depends on the libfuse version, and the
other's message ("mountpoint is not empty" on the fallback, "unknown option" on
the first) is the misleading one. Verified in avocadolinux/sdk:2026 that a
failed mount now reports "fuse: device not found" rather than the empty-mount
red herring.

The mount-time warning leads with the action, since the old suffix landed on
the last line of a multi-line message. Adds the Fixed entries under
[Unreleased].
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.

3 participants