Test: der Claude-Code-Weg in der echten Sandbox, mit Stellvertreter (#712)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7a8852fe6b
commit
4940fdefb0
@@ -186,6 +186,65 @@ public sealed class McpSocketTests : IDisposable
|
|||||||
outcome.Text.ShouldContain("added: result.txt");
|
outcome.Text.ShouldContain("added: result.txt");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// #712: The Claude Code path in the real sandbox, with a stand-in for <c>claude</c> —
|
||||||
|
/// the real one does not run on the test machine, and a test has no key. The stand-in
|
||||||
|
/// checks what Claude Code would be given (arguments, key, home, MCP configuration),
|
||||||
|
/// reaches the API host through the proxy, calls the MCP server the way Claude Code
|
||||||
|
/// would (the command from mcp.json) and answers in stream-json.
|
||||||
|
/// </summary>
|
||||||
|
[ForeignRunFact]
|
||||||
|
public async Task The_claude_code_path_holds_together_in_the_real_sandbox()
|
||||||
|
{
|
||||||
|
await using var host = NewHost();
|
||||||
|
await using var web = InstanceHttpHost.TryStart(host, NullLoggerFactory.Instance);
|
||||||
|
web.ShouldNotBeNull();
|
||||||
|
|
||||||
|
var bin = Path.Combine(_root, "fake-claude");
|
||||||
|
Directory.CreateDirectory(bin);
|
||||||
|
var claude = Path.Combine(bin, "claude");
|
||||||
|
File.WriteAllText(claude, string.Join("\n",
|
||||||
|
"#!/bin/sh",
|
||||||
|
"""case " $* " in *dangerously*) echo "bypass passed" >&2; exit 9;; esac""",
|
||||||
|
"""[ -n "$ANTHROPIC_API_KEY" ] || { echo "no key" >&2; exit 8; }""",
|
||||||
|
"""[ -w "$HOME/.claude/settings.json" ] || { echo "no home" >&2; exit 7; }""",
|
||||||
|
"""prompt=$(cat)""",
|
||||||
|
"""api=$(curl -sS -o /dev/null -w '%{http_code}' --max-time 20 https://api.anthropic.com/ 2>&1)""",
|
||||||
|
"""cmd=$(sed -n 's/.*"command":"\([^"]*\)".*/\1/p' /run/agent/mcp.json)""",
|
||||||
|
"""tools=$(printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | CLAWD_MCP_TOKEN_FILE=/run/agent/mcp-token "$cmd" --mcp-bridge --socket /run/mcp.sock 2>&1 | tr -d '"')""",
|
||||||
|
"""echo "changed by claude" > /work/answer.txt""",
|
||||||
|
"""printf '%s\n' '{"type":"system","subtype":"init","model":"claude-test"}'""",
|
||||||
|
"""printf '{"type":"result","subtype":"success","is_error":false,"num_turns":1,"result":"prompt=%s api=%s tools=%s","total_cost_usd":0.01,"usage":{"input_tokens":10,"output_tokens":5}}\n' "$prompt" "$api" "$tools" """,
|
||||||
|
""));
|
||||||
|
if (OperatingSystem.IsLinux())
|
||||||
|
File.SetUnixFileMode(claude, UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute);
|
||||||
|
|
||||||
|
var sandbox = (Core.Sandbox.BwrapSandbox)Core.Sandbox.BwrapSandbox.Detect(NullLogger.Instance);
|
||||||
|
var helper = Core.Sandbox.SandboxHelper.For(ForeignRunFactAttribute.HelperPath!);
|
||||||
|
sandbox.Helper = helper;
|
||||||
|
var settings = new Core.Runtimes.ClaudeCodeSettings { Executable = claude, ApiKey = "sk-ant-fake" };
|
||||||
|
var runner = new Core.Runtimes.ForeignRunner(sandbox, [new Core.Runtimes.ClaudeCodeRuntime(() => settings)], host.Mcp,
|
||||||
|
InstanceEndpoint.McpSocketPath(host.InstancePath), helper, NullLogger.Instance);
|
||||||
|
|
||||||
|
var agentDir = Path.Combine(host.InstancePath, "Agents", "Agent-coder");
|
||||||
|
Directory.CreateDirectory(Path.Combine(agentDir, "Workspace"));
|
||||||
|
var agent = new AgentConfig
|
||||||
|
{
|
||||||
|
AgentId = "coder", AgentDir = agentDir, WorkspacePath = Path.Combine(agentDir, "Workspace"),
|
||||||
|
Runtime = new Core.Runtimes.AgentRuntimeConfig { Kind = "claude-code", TimeoutMinutes = 2 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var outcome = await runner.RunAsync(new Core.Runtimes.ForeignRunRequest(agent, "run-cc", null, "brief", "fix it"), _ => { }, CancellationToken.None);
|
||||||
|
|
||||||
|
outcome.Status.ShouldBe(AgentRunStatus.Completed, outcome.Text);
|
||||||
|
outcome.Text!.ShouldContain("prompt=fix it");
|
||||||
|
outcome.Text.ShouldNotContain("api=000", customMessage: "the API host is reached through the proxy");
|
||||||
|
outcome.Text.ShouldContain("name:Echo");
|
||||||
|
outcome.Text.ShouldContain("added: answer.txt");
|
||||||
|
outcome.Model.ShouldBe("claude-test");
|
||||||
|
outcome.CostUsd.ShouldBe(0.01m);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class EchoTool : IAgentTool
|
private sealed class EchoTool : IAgentTool
|
||||||
{
|
{
|
||||||
public string Name => "Echo";
|
public string Name => "Echo";
|
||||||
|
|||||||
Reference in New Issue
Block a user