Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions abx_plugins/plugins/apt/on_BinaryRequest__13_apt.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def main(

# Use abxpkg AptProvider to install binary
provider = AptProvider()
if not provider.INSTALLER_BIN_ABSPATH:
try:
provider.INSTALLER_BINARY()
except Exception:
click.echo(
"AptProvider.INSTALLER_BIN is not available on this host",
err=True,
Expand Down Expand Up @@ -78,7 +80,7 @@ def main(
err=True,
)

binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"apt install failed: {e}", err=True)
sys.exit(1)
Expand Down
15 changes: 13 additions & 2 deletions abx_plugins/plugins/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,8 +780,13 @@ def enforce_lib_permissions(config_dir: Path | str | None = None) -> None:
for fname in filenames:
fp = dp / fname
_chown_if_needed(fp, target_uid, target_gid)
# Preserve execute bit for binaries
current = fp.stat().st_mode
# Preserve execute bit for binaries. Use lstat() so dangling
# symlinks (which are valid, just unresolved) don't crash the
# permission enforcer; skip chmod for symlinks since chmod()
# would follow them and fail the same way.
current = fp.lstat().st_mode
if stat.S_ISLNK(current):
continue
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
if current & stat.S_IXUSR:
fp.chmod(0o755) # rwxr-xr-x (executable)
else:
Expand All @@ -799,9 +804,15 @@ def enforce_lib_permissions(config_dir: Path | str | None = None) -> None:
for fname in filenames:
fp = dp / fname
_chown_if_needed(fp, target_uid, target_gid)
# Skip symlinks — chmod would follow them and raise on
# a dangling target, mirroring the lib/ tree walk above.
if stat.S_ISLNK(fp.lstat().st_mode):
continue
fp.chmod(0o644)
elif entry.is_file():
_chown_if_needed(entry, target_uid, target_gid)
if stat.S_ISLNK(entry.lstat().st_mode):
continue
entry.chmod(0o644)


Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/bash/on_BinaryRequest__14_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def main(
sys.exit(1)

try:
binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"bash install failed: {e}", err=True)
sys.exit(1)
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/bash/tests/test_bash_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def test_hook_fails_for_missing_binary_after_command(self):

# Should fail since binary not found after command
assert result.returncode == 1
assert "unable to load or install binary nonexistent_binary_xyz123" in (
assert "unable to install binary nonexistent_binary_xyz123" in (
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P3: Fix the expected stderr text to match the hook's actual failure message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/bash/tests/test_bash_provider.py, line 131:

<comment>Fix the expected stderr text to match the hook's actual failure message.</comment>

<file context>
@@ -128,7 +128,7 @@ def test_hook_fails_for_missing_binary_after_command(self):
         # Should fail since binary not found after command
         assert result.returncode == 1
-        assert "unable to load or install binary nonexistent_binary_xyz123" in (
+        assert "unable to install binary nonexistent_binary_xyz123" in (
             result.stderr.lower()
         )
</file context>
Suggested change
assert "unable to install binary nonexistent_binary_xyz123" in (
assert "nonexistent_binary_xyz123 not found after bash install" in (
Fix with Cubic

result.stderr.lower()
)

Expand Down
7 changes: 2 additions & 5 deletions abx_plugins/plugins/brew/on_BinaryRequest__12_brew.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ def main(

# Use abxpkg BrewProvider to install binary
provider = BrewProvider()
if not provider.INSTALLER_BIN_ABSPATH:
click.echo("brew not available on this system", err=True)
sys.exit(0)
try:
Binary(name=provider.INSTALLER_BIN, binproviders=[provider]).load()
provider.INSTALLER_BINARY()
except Exception:
click.echo("brew not available on this system", err=True)
sys.exit(0)
Expand All @@ -79,7 +76,7 @@ def main(
err=True,
)

binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"brew install failed: {e}", err=True)
sys.exit(1)
Expand Down
6 changes: 4 additions & 2 deletions abx_plugins/plugins/cargo/on_BinaryRequest__12_cargo.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def main(
sys.exit(0)

provider = CargoProvider()
if not provider.INSTALLER_BIN_ABSPATH:
try:
provider.INSTALLER_BINARY()
except Exception:
click.echo("cargo not available on this system", err=True)
sys.exit(0)

Expand All @@ -74,7 +76,7 @@ def main(
err=True,
)

binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"cargo install failed: {e}", err=True)
sys.exit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def main(
]:
sys.exit(0)

provider = ChromeWebstoreProvider(extensions_dir=_extensions_dir())
provider = ChromeWebstoreProvider(bin_dir=_extensions_dir())
if not provider.is_valid:
click.echo("chromewebstore provider is not available on this host", err=True)
sys.exit(0)
Expand All @@ -72,7 +72,7 @@ def main(
"min_version": min_version or extra_kwargs.get("min_version") or None,
"overrides": json.loads(overrides) if overrides else {},
},
).load_or_install()
).install()

if not binary.abspath:
click.echo(f"{name} not resolved as Chrome Web Store extension", err=True)
Expand Down
1 change: 1 addition & 0 deletions abx_plugins/plugins/claudecode/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"name": "{CLAUDECODE_BINARY}",
"binproviders": "env,npm",
"min_version": null,
"postinstall_scripts": true,
"overrides": {
"npm": {
"install_args": [
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/defuddle/tests/test_defuddle.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_defuddle_binary_path() -> str | None:
name="defuddle",
binproviders=[NpmProvider(), EnvProvider()],
overrides={"npm": {"install_args": ["defuddle"]}},
).load_or_install()
).install()
if binary and binary.abspath:
_defuddle_binary_path = str(binary.abspath)
return _defuddle_binary_path
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/forumdl/tests/test_forumdl.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_forumdl_binary_path() -> str | None:
],
},
},
).load_or_install()
).install()
if binary and binary.abspath:
_forumdl_binary_path = str(binary.abspath)
return _forumdl_binary_path
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/gallerydl/tests/test_gallerydl.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ def get_gallerydl_binary_path() -> str | None:
binary = Binary(
name="gallery-dl",
binproviders=[PipProvider(), EnvProvider()],
).load_or_install()
).install()
if binary and binary.abspath:
_gallerydl_binary_path = str(binary.abspath)
return _gallerydl_binary_path
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/liteparse/tests/test_liteparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def get_liteparse_binary_path() -> str | None:
name="lit",
binproviders=[NpmProvider(), EnvProvider()],
overrides={"npm": {"install_args": ["@llamaindex/liteparse"]}},
).load_or_install()
).install()
if binary and binary.abspath:
_liteparse_binary_path = str(binary.abspath)
return _liteparse_binary_path
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/mercury/tests/test_mercury.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def get_mercury_binary_path() -> str | None:
name="postlight-parser",
binproviders=[NpmProvider(), EnvProvider()],
overrides={"npm": {"install_args": ["@postlight/parser"]}},
).load_or_install()
).install()
if binary and binary.abspath:
_mercury_binary_path = str(binary.abspath)
return _mercury_binary_path
Expand Down
8 changes: 5 additions & 3 deletions abx_plugins/plugins/npm/on_BinaryRequest__10_npm.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ def main(
npm_prefix.mkdir(parents=True, exist_ok=True)

# Use abxpkg NpmProvider to install binary with custom prefix
provider = NpmProvider(npm_prefix=npm_prefix)
if not provider.INSTALLER_BIN_ABSPATH:
provider = NpmProvider(install_root=npm_prefix)
try:
provider.INSTALLER_BINARY()
except Exception:
click.echo("npm not available on this system", err=True)
sys.exit(0)

Expand Down Expand Up @@ -96,7 +98,7 @@ def main(
os.environ["PUPPETEER_SKIP_DOWNLOAD"] = "true"
os.environ["PUPPETEER_SKIP_CHROMIUM_DOWNLOAD"] = "true"

binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"npm install failed: {e}", err=True)
sys.exit(1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def get_opendataloader_binary_path() -> str | None:
name="opendataloader-pdf",
binproviders=[PipProvider(), EnvProvider()],
overrides={"pip": {"install_args": ["opendataloader-pdf"]}},
).load_or_install()
).install()
if binary and binary.abspath:
_opendataloader_binary_path = str(binary.abspath)
return _opendataloader_binary_path
Expand Down Expand Up @@ -83,7 +83,7 @@ def get_java_binary_path() -> str | None:
"brew": {"install_args": ["openjdk"]},
"apt": {"install_args": ["default-jre"]},
},
).load_or_install()
).install()
if binary and binary.abspath:
_java_binary_path = str(binary.abspath)
return _java_binary_path
Expand Down
8 changes: 5 additions & 3 deletions abx_plugins/plugins/pip/on_BinaryRequest__11_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,10 @@ def main(
_seed_pip_venv(pip_venv_path, preferred_python)

# Use abxpkg PipProvider to install binary with custom venv
provider = PipProvider(pip_venv=pip_venv_path)
if not provider.INSTALLER_BIN_ABSPATH:
provider = PipProvider(install_root=pip_venv_path)
try:
provider.INSTALLER_BINARY()
except Exception:
click.echo("pip not available on this system", err=True)
sys.exit(0)

Expand All @@ -197,7 +199,7 @@ def main(
err=True,
)

binary = binary.load_or_install()
binary = binary.install()
except Exception as e:
click.echo(f"pip install failed: {e}", err=True)
sys.exit(1)
Expand Down
5 changes: 0 additions & 5 deletions abx_plugins/plugins/puppeteer/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
"type": "boolean",
"default": true,
"description": "Enable Puppeteer dependency installation during crawl setup"
},
"PUPPETEER_CACHE_DIR": {
"type": "string",
"default": "",
"description": "Override the Puppeteer browser cache directory"
}
}
}
17 changes: 4 additions & 13 deletions abx_plugins/plugins/puppeteer/on_BinaryRequest__12_puppeteer.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,9 @@ def main(
if not lib_dir:
lib_dir = str(Path.home() / ".config" / "abx" / "lib")

configured_cache_dir = (config.PUPPETEER_CACHE_DIR or "").strip()
if configured_cache_dir:
browser_cache_dir = Path(configured_cache_dir).expanduser().resolve()
browser_cache_dir.mkdir(parents=True, exist_ok=True)
provider = PuppeteerProvider(
browser_cache_dir=browser_cache_dir,
browser_bin_dir=browser_cache_dir.parent / "bin",
)
else:
install_root = (Path(lib_dir) / "puppeteer").resolve()
install_root.mkdir(parents=True, exist_ok=True)
provider = PuppeteerProvider(install_root=install_root)
install_root = (Path(lib_dir) / "puppeteer").resolve()
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Apr 21, 2026

Choose a reason for hiding this comment

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

P2: This change drops support for PUPPETEER_CACHE_DIR. The config option is still documented, but the new code always uses lib_dir/puppeteer, so user overrides are ignored.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At abx_plugins/plugins/puppeteer/on_BinaryRequest__12_puppeteer.py, line 70:

<comment>This change drops support for `PUPPETEER_CACHE_DIR`. The config option is still documented, but the new code always uses `lib_dir/puppeteer`, so user overrides are ignored.</comment>

<file context>
@@ -67,28 +67,9 @@ def main(
-        install_root = (Path(lib_dir) / "puppeteer").resolve()
-        install_root.mkdir(parents=True, exist_ok=True)
-        provider = PuppeteerProvider(install_root=install_root)
+    install_root = (Path(lib_dir) / "puppeteer").resolve()
+    install_root.mkdir(parents=True, exist_ok=True)
+    provider = PuppeteerProvider(install_root=install_root)
</file context>
Fix with Cubic

install_root.mkdir(parents=True, exist_ok=True)
provider = PuppeteerProvider(install_root=install_root)

raw_overrides = json.loads(overrides) if overrides else {}
if not isinstance(raw_overrides, dict):
Expand Down Expand Up @@ -115,7 +106,7 @@ def main(
"binproviders": [provider],
"overrides": raw_overrides,
},
).load_or_install()
).install()
except Exception as e:
error_output = str(e)
hint = _get_install_failure_hint(error_output)
Expand Down
2 changes: 1 addition & 1 deletion abx_plugins/plugins/readability/tests/test_readability.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def get_readability_binary_path() -> str | None:
"install_args": ["https://github.com/ArchiveBox/readability-extractor"],
},
},
).load_or_install()
).install()
if binary and binary.abspath:
_readability_binary_path = str(binary.abspath)
return _readability_binary_path
Expand Down
4 changes: 2 additions & 2 deletions abx_plugins/plugins/ytdlp/tests/test_ytdlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def get_ytdlp_binary_path() -> str | None:
name="yt-dlp",
binproviders=[PipProvider(), EnvProvider()],
overrides={"pip": {"install_args": ["yt-dlp[default]"]}},
).load_or_install()
).install()
if binary and binary.abspath:
_ytdlp_binary_path = str(binary.abspath)
return _ytdlp_binary_path
Expand All @@ -118,7 +118,7 @@ def require_ffmpeg_binary() -> str:
binary = Binary(
name="ffmpeg",
binproviders=[EnvProvider(), BrewProvider(), AptProvider()],
).load_or_install()
).install()
assert binary and binary.abspath, (
"ffmpeg installation failed. ytdlp tests require a real ffmpeg binary."
)
Expand Down
14 changes: 14 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,20 @@ def install_claude_code_with_hooks() -> str:
overrides = binary_record.get("overrides")
if overrides:
npm_cmd.append(f"--overrides={json.dumps(overrides)}")
# Forward any remaining top-level binary-record fields (e.g.
# ``postinstall_scripts``, ``min_release_age``) so the npm hook
# sees them as Binary kwargs via ``parse_extra_hook_args``.
_forwarded = {
key: value
for key, value in binary_record.items()
if key not in {"name", "binproviders", "overrides", "min_version"}
and value is not None
}
for key, value in _forwarded.items():
flag = "--" + key.replace("_", "-")
npm_cmd.append(
f"{flag}={json.dumps(value) if not isinstance(value, str) else value}",
)

npm_result = subprocess.run(
npm_cmd,
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ addopts = ["-p", "abx_plugins.pytest_bootstrap", "-p", "no:cacheprovider"]

[tool.uv]
exclude-newer = "14 days"
exclude-newer-package = { abxpkg = "1 second"}
exclude-newer-package = { abxpkg = "1 second", abx-plugins = "1 second" }

[tool.pyright]
include = [
Expand Down
Loading