Skip to content

fix: improve binary install experience#8

Merged
beonde merged 2 commits intomainfrom
fix/binary-install-experience
Mar 11, 2026
Merged

fix: improve binary install experience#8
beonde merged 2 commits intomainfrom
fix/binary-install-experience

Conversation

@beonde
Copy link
Member

@beonde beonde commented Mar 11, 2026

Summary

Fixes binary install experience issues identified in the install audit. Companion to capiscio/capiscio-sdk-python#38.

P0 Fixes

  • Visible download output: First-run binary download now writes to stderr so users see progress even without logging configured
  • Version mismatch: CORE_MIN_VERSION updated from 2.3.0 to 2.4.0 to match SDK
  • Error message: Health check error now uses CORE_MIN_VERSION/CORE_MAX_VERSION constants instead of hardcoded 2.5.0

P1 Fixes

  • Shared cache directory: Changed from platformdirs.user_cache_dir("capiscio-mcp") to ~/.capiscio/bin/ so SDK and MCP share the same binary cache
  • Removed platformdirs dependency: No longer needed after cache dir change
  • Retry with backoff: Download retries 3 times with exponential backoff (1s, 2s) before failing

Files Changed

  • capiscio_mcp/_core/lifecycle.py — Download visibility, shared cache, retry logic
  • capiscio_mcp/_core/version.py — Version bump
  • capiscio_mcp/_core/health.py — Dynamic error message
  • pyproject.toml — Remove platformdirs dep

Testing

  • All 350 unit tests passing

- Add visible stderr output during binary download (P0)
- Fix CORE_MIN_VERSION 2.3.0 -> 2.4.0 to match SDK (P0)
- Fix hardcoded version string in health check error message (P0)
- Align cache directory to ~/.capiscio/bin/ (shared with SDK) (P1)
- Remove unused platformdirs dependency (P1)
- Add retry with exponential backoff for download (3 attempts) (P1)
Copilot AI review requested due to automatic review settings March 11, 2026 19:28
@github-actions
Copy link

✅ Integration tests passed! capiscio-core gRPC tests working.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Updates capiscio-core compatibility requirements and binary lifecycle behavior to align with the current MCP SDK expectations, while simplifying cache-path handling and dependency set.

Changes:

  • Removed platformdirs dependency and switched binary cache location to ~/.capiscio/bin.
  • Raised CORE_MIN_VERSION to 2.4.0 and updated version compatibility messaging to use constants.
  • Added retry/backoff logic and user-facing stderr output during binary download/install.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
pyproject.toml Drops platformdirs dependency after cache-dir implementation change.
capiscio_mcp/_core/version.py Raises minimum compatible capiscio-core version to 2.4.0.
capiscio_mcp/_core/lifecycle.py Changes cache dir path and adds download retry/backoff plus stderr output.
capiscio_mcp/_core/health.py Uses version constants in the incompatibility error message (and expands imports).

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +126 to +130
sys.stderr.write(
f"capiscio-core v{version} not found. "
f"Downloading for {os_name}/{arch_name}...\n"
)
sys.stderr.flush()
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Avoid writing directly to sys.stderr from this library function. It produces unconfigurable output for SDK consumers (including tests/host apps). Prefer logger output (or gate the message behind an explicit verbose/debug flag).

Copilot uses AI. Check for mistakes.
Comment on lines +136 to +167
max_attempts = 3
for attempt in range(1, max_attempts + 1):
try:
response = requests.get(url, stream=True, timeout=60)
response.raise_for_status()

# Write binary
with open(target_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)

# Make executable (Unix)
if os_name != "windows":
st = os.stat(target_path)
os.chmod(target_path, st.st_mode | stat.S_IEXEC)

sys.stderr.write(f"Installed capiscio-core v{version} at {target_path}\n")
sys.stderr.flush()
logger.info(f"Successfully installed capiscio-core v{version}")
return target_path

except Exception as e:
if target_path.exists():
target_path.unlink()
if attempt < max_attempts:
delay = 2 ** (attempt - 1)
logger.warning(
f"Download attempt {attempt}/{max_attempts} failed: {e}. "
f"Retrying in {delay}s..."
)
time.sleep(delay)
else:
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This retry loop will also retry (and sleep) on permanent failures like HTTP 404/400 from raise_for_status(), which adds avoidable startup latency and slows the existing unit test that mocks a 404. Consider retrying only on transient errors (timeouts/connection errors/5xx), and fail fast on 4xx.

Copilot uses AI. Check for mistakes.
Comment on lines +139 to +146
response = requests.get(url, stream=True, timeout=60)
response.raise_for_status()

# Write binary
with open(target_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)

Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The streaming download doesn't explicitly close the HTTP response. Using a context manager (or calling response.close()) helps avoid leaking connections across retries and keeps the requests connection pool healthy.

Suggested change
response = requests.get(url, stream=True, timeout=60)
response.raise_for_status()
# Write binary
with open(target_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)
with requests.get(url, stream=True, timeout=60) as response:
response.raise_for_status()
# Write binary
with open(target_path, "wb") as f:
for chunk in response.iter_content(chunk_size=8192):
f.write(chunk)

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +154
sys.stderr.write(f"Installed capiscio-core v{version} at {target_path}\n")
sys.stderr.flush()
logger.info(f"Successfully installed capiscio-core v{version}")
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Avoid writing directly to sys.stderr on successful install as well; this is noisy for library consumers. Prefer logging (or an opt-in progress callback/flag) so callers can control where output goes.

Copilot uses AI. Check for mistakes.
from typing import TYPE_CHECKING

from capiscio_mcp._core.version import is_core_compatible, PROTO_VERSION
from capiscio_mcp._core.version import is_core_compatible, PROTO_VERSION, CORE_MIN_VERSION, CORE_MAX_VERSION
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

This import line exceeds the configured 100-char line length and will be reformatted by Black. Please wrap it using parentheses to keep formatting consistent with the repo's Black configuration.

Suggested change
from capiscio_mcp._core.version import is_core_compatible, PROTO_VERSION, CORE_MIN_VERSION, CORE_MAX_VERSION
from capiscio_mcp._core.version import (
is_core_compatible,
PROTO_VERSION,
CORE_MIN_VERSION,
CORE_MAX_VERSION,
)

Copilot uses AI. Check for mistakes.
- Replace sys.stderr.write with logger.info for download messages
- Use requests.get as context manager to prevent connection leaks
- Only retry on transient errors (5xx/timeout/connection); fail fast on 4xx
- Wrap long import line in health.py for Black compliance
- Remove unused sys import
- Update 404 test mock for context manager + fast-fail behavior
@github-actions
Copy link

✅ Integration tests passed! capiscio-core gRPC tests working.

@beonde beonde merged commit c2f69fd into main Mar 11, 2026
10 checks passed
@beonde beonde deleted the fix/binary-install-experience branch March 11, 2026 20:28
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.

2 participants