Skip to content
Closed
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
5 changes: 1 addition & 4 deletions scripts/check_default_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ def _check_image_exists(image: str) -> tuple[bool, str]:
except subprocess.TimeoutExpired:
return (
False,
(
"timed out after "
f"{MANIFEST_CHECK_TIMEOUT_SECONDS}s while checking docker manifest"
),
(f"timed out after {MANIFEST_CHECK_TIMEOUT_SECONDS}s while checking docker manifest"),
)
if proc.returncode == 0:
return True, ""
Expand Down
4 changes: 1 addition & 3 deletions src/vibepod/commands/attach.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,7 @@ def attach(

agent = (getattr(target, "labels", {}) or {}).get("vibepod.agent", "agent")
info(f"Attaching to {target.name} ({agent})")
warning(
f"Close the terminal to leave it running, or stop it with `vp stop {target.name}`."
)
warning(f"Close the terminal to leave it running, or stop it with `vp stop {target.name}`.")
try:
manager.attach_interactive(target)
except DockerClientError as exc:
Expand Down
12 changes: 3 additions & 9 deletions src/vibepod/commands/doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,7 @@ def claude() -> None:
console.print(f" {key}: {masked}")
if not found_any:
console.print(" none set on host")
console.print(
" [dim]note: these are host-side; the container sees its own env.[/dim]"
)
console.print(" [dim]note: these are host-side; the container sees its own env.[/dim]")

console.print()
console.print("[bold]Effective auth mode on next `vp run claude`[/bold]")
Expand All @@ -196,12 +194,8 @@ def claude() -> None:
" • If `modified` on .credentials.json never updates past the original /login time,"
)
console.print(" the token is not being rotated. Re-run with:")
console.print(
" [cyan]vp run claude -e ANTHROPIC_LOG=debug -e DEBUG=1[/cyan]"
)
console.print(
" and look for [dim][API:auth][/dim] entries near/after expiry to confirm."
)
console.print(" [cyan]vp run claude -e ANTHROPIC_LOG=debug -e DEBUG=1[/cyan]")
console.print(" and look for [dim][API:auth][/dim] entries near/after expiry to confirm.")
console.print(
" • For headless/CI, consider `claude setup-token` + "
"`-e CLAUDE_CODE_OAUTH_TOKEN=...` to bypass refresh entirely."
Expand Down
6 changes: 1 addition & 5 deletions src/vibepod/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,11 +625,7 @@ def run(
finally:
logger.close_session(exit_reason)

if (
selected_agent == "claude"
and "setup-token" in passthrough_args
and exit_reason == "normal"
):
if selected_agent == "claude" and "setup-token" in passthrough_args and exit_reason == "normal":
_capture_claude_setup_token(config_dir)


Expand Down
3 changes: 1 addition & 2 deletions src/vibepod/commands/skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ def add_cmd(
if record.get("bundle"):
installed = record.get("installed", [])
success(
f"Installed {len(installed)} skill(s) from bundle "
f"{record.get('locator', '')}"
f"Installed {len(installed)} skill(s) from bundle {record.get('locator', '')}"
)
for item in installed:
info(f" + {item.get('id', '?')} ({item.get('name', '')})")
Expand Down
4 changes: 1 addition & 3 deletions src/vibepod/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
_CLOSED_FILE_ERROR = "I/O operation on closed file."


def should_ignore_closed_http_response_flush_error(
response: object, exc: BaseException
) -> bool:
def should_ignore_closed_http_response_flush_error(response: object, exc: BaseException) -> bool:
"""Return True for Python 3.14's closed ``HTTPResponse.fp`` flush cleanup error.

Python 3.14 can surface a ``ValueError`` while finalizing Docker SDK / urllib3
Expand Down
8 changes: 2 additions & 6 deletions src/vibepod/core/docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,13 +689,9 @@ def stop_container(self, name_or_id: str, force: bool = False) -> Any:
try:
container.stop(timeout=0 if force else 10)
except APIError as exc:
raise DockerClientError(
f"Failed to stop container '{name_or_id}': {exc}"
) from exc
raise DockerClientError(f"Failed to stop container '{name_or_id}': {exc}") from exc
except DockerException as exc:
raise DockerClientError(
f"Failed to stop container '{name_or_id}': {exc}"
) from exc
raise DockerClientError(f"Failed to stop container '{name_or_id}': {exc}") from exc
return container

def stop_all(self, force: bool = False) -> int:
Expand Down
8 changes: 4 additions & 4 deletions src/vibepod/core/skills_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,9 @@ def run_engine(

config = get_config()
auto_pull_enabled = bool(config.get("auto_pull", True))
is_latest = (
":" not in SKILLS_ENGINE_IMAGE.split("/")[-1]
or SKILLS_ENGINE_IMAGE.endswith(":latest")
)
is_latest = ":" not in SKILLS_ENGINE_IMAGE.split("/")[
-1
] or SKILLS_ENGINE_IMAGE.endswith(":latest")

if not image_exists:
manager.pull_image(
Expand All @@ -171,6 +170,7 @@ def run_engine(
elif auto_pull_enabled and is_latest:
try:
from vibepod.utils.console import info

info("Checking for skills-engine image updates…")
manager.pull_if_newer(
SKILLS_ENGINE_IMAGE,
Expand Down
1 change: 0 additions & 1 deletion tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ def test_llm_env_overrides(monkeypatch, tmp_path: Path) -> None:
assert llm["model"] == "llama3"



# ---------------------------------------------------------------------------
# allow-dir / remove-dir / list-allowed-dirs subcommand tests
# ---------------------------------------------------------------------------
Expand Down
28 changes: 7 additions & 21 deletions tests/test_doctor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ def test_doctor_missing_dir(tmp_path: Path, monkeypatch) -> None:


def test_doctor_valid_token(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
future_ms = int((time.time() + 3600) * 1000)
(tmp_path / ".credentials.json").write_text(
json.dumps(
Expand All @@ -46,9 +44,7 @@ def test_doctor_valid_token(tmp_path: Path, monkeypatch) -> None:


def test_doctor_expired_token(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
past_ms = int((time.time() - 3600) * 1000)
Expand All @@ -70,16 +66,12 @@ def test_doctor_expired_token(tmp_path: Path, monkeypatch) -> None:

def test_doctor_expired_creds_but_stored_token_is_ok(tmp_path: Path, monkeypatch) -> None:
"""Expired credentials.json should NOT exit 2 when a stored token covers auth."""
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
past_ms = int((time.time() - 3600) * 1000)
(tmp_path / ".credentials.json").write_text(
json.dumps(
{"claudeAiOauth": {"accessToken": "a", "expiresAt": past_ms}}
)
json.dumps({"claudeAiOauth": {"accessToken": "a", "expiresAt": past_ms}})
)
(tmp_path / "oauth-token").write_text("sk-stored\n", encoding="utf-8")
result = runner.invoke(app, ["doctor", "claude"])
Expand All @@ -88,9 +80,7 @@ def test_doctor_expired_creds_but_stored_token_is_ok(tmp_path: Path, monkeypatch


def test_doctor_missing_refresh_token(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
future_ms = int((time.time() + 3600) * 1000)
(tmp_path / ".credentials.json").write_text(
json.dumps(
Expand All @@ -108,9 +98,7 @@ def test_doctor_missing_refresh_token(tmp_path: Path, monkeypatch) -> None:


def test_doctor_reports_stored_token_mode(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.delenv("CLAUDE_CODE_OAUTH_TOKEN", raising=False)
(tmp_path / "oauth-token").write_text("sk-xyz\n", encoding="utf-8")
Expand All @@ -120,9 +108,7 @@ def test_doctor_reports_stored_token_mode(tmp_path: Path, monkeypatch) -> None:


def test_doctor_reports_host_env_mode(tmp_path: Path, monkeypatch) -> None:
monkeypatch.setattr(
"vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path
)
monkeypatch.setattr("vibepod.commands.doctor.agent_config_dir", lambda _agent: tmp_path)
monkeypatch.delenv("ANTHROPIC_API_KEY", raising=False)
monkeypatch.setenv("CLAUDE_CODE_OAUTH_TOKEN", "host-token-abc")
result = runner.invoke(app, ["doctor", "claude"])
Expand Down
11 changes: 4 additions & 7 deletions tests/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,9 +679,9 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]

run_cmd.run(agent="claude", workspace=tmp_path, detach=True, paste_images=True)

assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get(
"extra_volumes", []
), f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}"
assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get("extra_volumes", []), (
f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}"
)


def test_paste_images_flag_mounts_xauth_cookie(monkeypatch, tmp_path: Path) -> None:
Expand Down Expand Up @@ -1900,7 +1900,6 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
assert env["TERM"] == "xterm-256color"



# ---------------------------------------------------------------------------
# Directory permission tests
# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -1942,9 +1941,7 @@ def run_agent(self, **kwargs) -> object: # type: ignore[no-untyped-def]
return _CapturingDockerManager, captured


def test_run_aborts_when_dir_not_allowed_and_non_interactive(
monkeypatch, tmp_path: Path
) -> None:
def test_run_aborts_when_dir_not_allowed_and_non_interactive(monkeypatch, tmp_path: Path) -> None:
"""Non-interactive stdin + disallowed dir → Exit(1) with no prompt."""
monkeypatch.setattr(run_cmd, "is_dir_allowed", lambda p: False)
monkeypatch.setattr(run_cmd, "is_protected_dir", lambda p: False)
Expand Down
17 changes: 9 additions & 8 deletions tests/test_skills_engine_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ def mock_docker_manager(monkeypatch: pytest.MonkeyPatch) -> None:
class FakeDockerManager:
def __init__(self) -> None:
self.client = MagicMock()

def pull_image(self, image: str, auto_clean: bool = False) -> None:
pass

def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool:
return False

Expand Down Expand Up @@ -80,9 +82,7 @@ def test_run_engine_explicit_local_scope_creates_local_skills_dir(
skills_engine.list_skills("local", cwd=tmp_path)

local = tmp_path / ".vibepod" / "skills"
mount_args = [
arg for i, arg in enumerate(captured["cmd"]) if captured["cmd"][i - 1] == "-v"
]
mount_args = [arg for i, arg in enumerate(captured["cmd"]) if captured["cmd"][i - 1] == "-v"]
assert local.is_dir()
assert f"{local}:/vibepod/local-skills" in mount_args

Expand Down Expand Up @@ -150,8 +150,7 @@ def test_add_accepts_github_tree_url(monkeypatch: pytest.MonkeyPatch, tmp_path:
monkeypatch.setattr(subprocess, "run", fake_run)

url = (
"https://github.com/alirezarezvani/claude-skills/tree/main/"
"product-team/skills/spec-to-repo"
"https://github.com/alirezarezvani/claude-skills/tree/main/product-team/skills/spec-to-repo"
)
skills_engine.add(url, scope="user", cwd=tmp_path)

Expand Down Expand Up @@ -232,9 +231,7 @@ def test_add_does_not_mount_remote_locators(
assert "-w" not in cmd, locator


def test_add_expands_tilde_local_locator(
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
def test_add_expands_tilde_local_locator(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
home = tmp_path / "home"
source = home / "skills" / "foo"
source.mkdir(parents=True)
Expand Down Expand Up @@ -308,8 +305,10 @@ class FakeDockerManager:
def __init__(self) -> None:
self.client = MagicMock()
self.client.images.get.side_effect = NotFound("not found")

def pull_image(self, image: str, auto_clean: bool = False) -> None:
pulled_images.append((image, auto_clean))

def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool:
checked_images.append(image)
return False
Expand Down Expand Up @@ -341,8 +340,10 @@ class FakeDockerManager:
def __init__(self) -> None:
self.client = MagicMock()
self.client.images.get.return_value = MagicMock()

def pull_image(self, image: str, auto_clean: bool = False) -> None:
pulled_images.append((image, auto_clean))

def pull_if_newer(self, image: str, auto_clean: bool = False) -> bool:
checked_images.append((image, auto_clean))
return False
Expand Down
6 changes: 3 additions & 3 deletions tests/test_task_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,6 +1012,7 @@ def test_task_create_preserves_host_user_for_non_podman(
monkeypatch, tmp_path, tmp_task_store
) -> None:
import os

class _DockerDockerManager(_CapturingDockerManager):
def is_rootless_podman(self) -> bool:
return False
Expand All @@ -1021,15 +1022,14 @@ def is_rootless_podman(self) -> bool:
monkeypatch.setattr(task_cmd, "DockerManager", lambda: stub)

import dataclasses

original_get_agent_spec = task_cmd.get_agent_spec
spec = original_get_agent_spec("claude")
modified_spec = dataclasses.replace(spec, run_as_host_user=True)
monkeypatch.setattr(
task_cmd,
"get_agent_spec",
lambda agent: (
modified_spec if agent == "claude" else original_get_agent_spec(agent)
),
lambda agent: modified_spec if agent == "claude" else original_get_agent_spec(agent),
)
monkeypatch.setattr(os, "getuid", lambda: 1234, raising=False)
monkeypatch.setattr(os, "getgid", lambda: 5678, raising=False)
Expand Down