alishahryar1--free-claude-code
aba94d5c3c
## Problem Claude proxy environment policy was duplicated between `fcc-claude`, managed messaging, and live smoke drivers. Managed messaging preserved a legacy endpoint variable, inherited more Anthropic state, and did not disable nonessential traffic, so FCC-launched Claude sessions could drift apart. ## Changes | Before | After | | --- | --- | | `fcc-claude` and managed messaging assembled proxy environments independently. | One shared owner strips inherited Anthropic variables and configures proxy URL, auth, discovery, compaction, and nonessential-traffic policy. | | Managed messaging carried a `/v1` API URL and converted it back to a proxy root while also setting a legacy endpoint variable. | Managed messaging receives the loopback-safe proxy root and uses the supported `ANTHROPIC_BASE_URL` contract directly. | | Managed and interactive policy could diverge while smoke drivers duplicated both shapes. | `fcc-claude`, messaging, and Claude smoke drivers use the same canonical environment builder. | | Managed execution concerns were mixed with shared proxy policy. | Messaging adds only noninteractive process settings and keeps `--model opus` plus stream-JSON flags in command construction. | | IDE examples left nonessential Anthropic traffic enabled. | VS Code and JetBrains examples disable nonessential Anthropic traffic. | <!-- greptile_comment --> <details open><summary><h3>Greptile Summary</h3></summary> This PR centralizes Claude Code proxy environment setup for FCC-launched sessions. The main changes are: - Adds one shared builder for Claude proxy environment variables. - Routes managed messaging sessions through the same proxy policy as `fcc-claude`. - Strips inherited `ANTHROPIC_*` state before launching Claude. - Sets the nonessential-traffic disable flag for managed and interactive Claude launches. - Updates smoke tests, docs, and version metadata for the new proxy-root URL shape. </details> <h3>Confidence Score: 5/5</h3> This looks safe to merge. No blocking issues were found in the changed code. Managed Claude launches now use the shared environment builder, and the managed path now sets the nonessential-traffic disable flag. No files need attention. <details><summary><h3><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="20" align="absmiddle"></a> T-Rex Logs</h3></summary> **What T-Rex did** - The executable harness claude\_env\_policy\_harness.py was generated to enable direct module-level runtime proof without requiring a real Claude binary or live Anthropic credentials. - A focused pytest run was executed, and it completed with 14 tests passing and exit code 0, validating the harness workflow. <a href="https://app.greptile.com/trex/runs/14173386/artifacts"><picture><source media="(prefers-color-scheme: dark)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source media="(prefers-color-scheme: light)" srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img alt="View all artifacts" src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a> <sub><a href="https://www.greptile.com/trex"><img alt="T-Rex" src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg" height="14" align="absmiddle"></a> Ran code and verified through T-Rex</sub> </details> <details open><summary><h3>Important Files Changed</h3></summary> | Filename | Overview | |----------|----------| | src/free_claude_code/cli/claude_env.py | Adds the shared Claude proxy environment builder and canonical traffic-disable policy. | | src/free_claude_code/cli/managed/claude.py | Delegates managed Claude environment construction to the shared proxy builder. | | src/free_claude_code/runtime/application.py | Passes the loopback-safe proxy root into the managed Claude session manager. | | src/free_claude_code/cli/managed/session.py | Renames managed session URL state to use the proxy-root contract. | | src/free_claude_code/cli/managed/manager.py | Carries the proxy-root URL through manager-created managed sessions. | </details> <sub>Reviews (2): Last reviewed commit: ["Keep README focused on client setup"](https://github.com/alishahryar1/free-claude-code/commit/83739a2f8953771ec9e447fc83a2057b08708891) | [Re-trigger Greptile](https://app.greptile.com/api/retrigger?id=43684511)</sub> <!-- /greptile_comment -->
127 行
4.3 KiB
Python
127 行
4.3 KiB
Python
from unittest.mock import AsyncMock, MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_register_real_session_id_moves_pending_to_active_and_maps():
|
|
from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager
|
|
|
|
with patch(
|
|
"free_claude_code.cli.managed.manager.ManagedClaudeSession"
|
|
) as mock_session_cls:
|
|
mock_session = MagicMock()
|
|
mock_session.is_busy = False
|
|
mock_session.stop = AsyncMock(return_value=True)
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
manager = ManagedClaudeSessionManager(
|
|
workspace_path="/tmp",
|
|
proxy_root_url="http://x",
|
|
auth_token="proxy-token",
|
|
)
|
|
session, temp_id, is_new = await manager.get_or_create_session()
|
|
assert session is mock_session
|
|
assert is_new is True
|
|
mock_session_cls.assert_called_once()
|
|
assert mock_session_cls.call_args.kwargs["auth_token"] == "proxy-token"
|
|
|
|
ok = await manager.register_real_session_id(temp_id, "real_1")
|
|
assert ok is True
|
|
|
|
# Lookup via temp id should resolve to the real session id.
|
|
s2, sid2, is_new2 = await manager.get_or_create_session(session_id=temp_id)
|
|
assert s2 is mock_session
|
|
assert sid2 == "real_1"
|
|
assert is_new2 is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_register_real_session_id_missing_temp_id_returns_false():
|
|
from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager
|
|
|
|
manager = ManagedClaudeSessionManager(
|
|
workspace_path="/tmp", proxy_root_url="http://x"
|
|
)
|
|
ok = await manager.register_real_session_id("missing", "real_1")
|
|
assert ok is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_session_pending_stops_and_returns_true():
|
|
from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager
|
|
|
|
with patch(
|
|
"free_claude_code.cli.managed.manager.ManagedClaudeSession"
|
|
) as mock_session_cls:
|
|
mock_session = MagicMock()
|
|
mock_session.is_busy = False
|
|
mock_session.stop = AsyncMock(return_value=True)
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
manager = ManagedClaudeSessionManager(
|
|
workspace_path="/tmp", proxy_root_url="http://x"
|
|
)
|
|
_, temp_id, _ = await manager.get_or_create_session()
|
|
|
|
removed = await manager.remove_session(temp_id)
|
|
assert removed is True
|
|
mock_session.stop.assert_awaited_once()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_remove_session_active_removes_temp_mapping():
|
|
from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager
|
|
|
|
with patch(
|
|
"free_claude_code.cli.managed.manager.ManagedClaudeSession"
|
|
) as mock_session_cls:
|
|
mock_session = MagicMock()
|
|
mock_session.is_busy = False
|
|
mock_session.stop = AsyncMock(return_value=True)
|
|
mock_session_cls.return_value = mock_session
|
|
|
|
manager = ManagedClaudeSessionManager(
|
|
workspace_path="/tmp", proxy_root_url="http://x"
|
|
)
|
|
_, temp_id, _ = await manager.get_or_create_session()
|
|
await manager.register_real_session_id(temp_id, "real_1")
|
|
|
|
removed = await manager.remove_session("real_1")
|
|
assert removed is True
|
|
|
|
# Temp ID should no longer resolve to an active session after removal.
|
|
_, sid2, is_new2 = await manager.get_or_create_session(session_id=temp_id)
|
|
assert sid2 == temp_id
|
|
assert is_new2 is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_stop_all_reports_and_retains_stop_exceptions():
|
|
from free_claude_code.cli.managed.manager import ManagedClaudeSessionManager
|
|
|
|
manager = ManagedClaudeSessionManager(
|
|
workspace_path="/tmp", proxy_root_url="http://x"
|
|
)
|
|
|
|
s1 = MagicMock()
|
|
s1.stop = AsyncMock(side_effect=RuntimeError("boom"))
|
|
s1.is_busy = False
|
|
|
|
s2 = MagicMock()
|
|
s2.stop = AsyncMock(return_value=True)
|
|
s2.is_busy = False
|
|
|
|
manager._sessions["a"] = s1
|
|
manager._pending_sessions["b"] = s2
|
|
|
|
with pytest.raises(
|
|
RuntimeError,
|
|
match=r"^Managed Claude session shutdown failures: 1\.$",
|
|
):
|
|
await manager.stop_all()
|
|
s1.stop.assert_awaited_once()
|
|
s2.stop.assert_awaited_once()
|
|
assert manager.get_stats()["active_sessions"] == 1
|
|
assert manager.get_stats()["pending_sessions"] == 0
|