From 86abf72c9a474b05afa2aaa6b2a75e39722ff025 Mon Sep 17 00:00:00 2001 From: robert Date: Mon, 29 Jun 2026 12:18:30 +1000 Subject: [PATCH] Remove Claude RunAs parameters --- .../RunAgentCommandFixture.cs | 3 - .../ClaudeCodeCliRunner.cs | 14 +- .../ClaudeCodeProcessStartInfo.cs | 178 +----------------- .../InvokeClaudeCodeBehaviour.cs | 16 -- source/Calamari.AiAgent/SpecialVariables.cs | 2 - 5 files changed, 4 insertions(+), 209 deletions(-) diff --git a/source/Calamari.AiAgent.Tests/RunAgentCommandFixture.cs b/source/Calamari.AiAgent.Tests/RunAgentCommandFixture.cs index a42111eba..c7846249a 100644 --- a/source/Calamari.AiAgent.Tests/RunAgentCommandFixture.cs +++ b/source/Calamari.AiAgent.Tests/RunAgentCommandFixture.cs @@ -101,7 +101,6 @@ public async Task ClaudeCode_SucceedsWithWebFetch() .WithArrange(context => { context.Variables.Add(SpecialVariables.Action.Claude.ApiToken, Environment.GetEnvironmentVariable("ANTHROPIC_TOKEN")); - context.Variables.Add(SpecialVariables.Action.Claude.RunAsUsername, "test-user"); context.Variables.Add(SpecialVariables.Action.Claude.Prompt, "get the currently executing process user"); }) .Execute(assertWasSuccess: false); @@ -118,8 +117,6 @@ public async Task ClaudeCode_RunsOn_RunsUnderAnotherAccount() .WithArrange(context => { context.Variables.Add(SpecialVariables.Action.Claude.ApiToken, Environment.GetEnvironmentVariable("ANTHROPIC_TOKEN")); - context.Variables.Add(SpecialVariables.Action.Claude.RunAsUsername, "test-user"); - context.Variables.Add(SpecialVariables.Action.Claude.RunAsPassword, "supersecret"); context.Variables.Add(SpecialVariables.Action.Claude.Prompt, "get the currently executing process user"); }) .Execute(assertWasSuccess: false); diff --git a/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeCliRunner.cs b/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeCliRunner.cs index 97f2ae8aa..a8c95167b 100644 --- a/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeCliRunner.cs +++ b/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeCliRunner.cs @@ -17,7 +17,6 @@ public class ClaudeCodeCliRunner(ILog log) public async Task RunAsync(ClaudeCommandArgsBuilder argsBuilder, Dictionary customEnvVars, - ProcessCredentials? runAs, string workingDir, string calamariDir, //RunAs might not be able to access this dir.. but we need to preserve the logs. CancellationToken cancellationToken) @@ -34,11 +33,9 @@ public async Task RunAsync(ClaudeCommandArgsBuilder argsBuilder, log.Verbose($"Claude Code command: {logFileName} {logArgs}"); var runner = new ClaudeCodeProcessStartInfo(); - var process = await runner.StartClaudeProcess(workingDir, - runAs, + var process = runner.StartClaudeProcess(workingDir, argsBuilder.WithDebugLogPath(debugLogPath), - customEnvVars, - cancellationToken); + customEnvVars); var responseBuilder = new StringBuilder(); var streamProcessor = new ClaudeCodeStreamProcessor(log, responseBuilder); @@ -114,13 +111,6 @@ async Task ProcessLine(Process process, ClaudeCodeStreamProcessor streamProcesso } } -public record ProcessCredentials -{ - public required string Username { get; init; } - public string? Password { get; init; } - public string? Domain { get; init; } -} - public record UserSkill { public required string Name { get; init; } diff --git a/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeProcessStartInfo.cs b/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeProcessStartInfo.cs index 79582dc5a..63a5dd441 100644 --- a/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeProcessStartInfo.cs +++ b/source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeProcessStartInfo.cs @@ -1,12 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks; -using Octopus.CoreUtilities.Extensions; namespace Calamari.AiAgent.ClaudeCodeBehaviour; @@ -34,32 +29,7 @@ SandboxMode.SandboxRuntime when string.IsNullOrWhiteSpace(argsBuilder.SandboxRun }; } - async Task StartWindowsProcess( - string workingDir, - ProcessCredentials? runAs, - ClaudeCommandArgsBuilder argsBuilder, - Dictionary environmentVariables) - { - var startInfo = StartSimpleProcess(workingDir, argsBuilder, environmentVariables); - - if (runAs != null) - { - startInfo.UserName = runAs.Username; -#pragma warning disable CA1416 - if (runAs.Password != null) - startInfo.PasswordInClearText = runAs.Password; - - if (!string.IsNullOrEmpty(runAs.Domain)) - startInfo.Domain = runAs.Domain; -#pragma warning restore CA1416 - } - - await Task.CompletedTask; - - return Process.Start(startInfo)!; - } - - static ProcessStartInfo StartSimpleProcess( + public Process StartClaudeProcess( string workingDir, ClaudeCommandArgsBuilder argsBuilder, Dictionary environmentVariables) @@ -81,150 +51,6 @@ static ProcessStartInfo StartSimpleProcess( startInfo.Environment[kvp.Key] = kvp.Value; } - return startInfo; - } - - public async Task StartClaudeProcess( - string workingDir, - ProcessCredentials? runAs, - ClaudeCommandArgsBuilder argsBuilder, - Dictionary environmentVariables, - CancellationToken ct) - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - return await StartWindowsProcess(workingDir, - runAs, - argsBuilder, - environmentVariables); - } - - return await StartMacOrLinuxProcess(workingDir, - runAs, - argsBuilder, - environmentVariables, - ct); - } - - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Higher up checks enforce the correct OS")] - async Task StartMacOrLinuxProcess( - string workingDir, - ProcessCredentials? runAs, - ClaudeCommandArgsBuilder argsBuilder, - Dictionary environmentVariables, - CancellationToken ct) - { - var username = runAs?.Username!; - if (runAs == null || string.IsNullOrEmpty(username)) - { - return Process.Start(StartSimpleProcess(workingDir, argsBuilder, environmentVariables)) - ?? throw new Exception("Failed to start the claude code process"); - } - - var (scriptFileName, scriptArgs) = ResolveInvocation(argsBuilder); - var filePath = Path.Combine(workingDir, "my-command.sh"); - await File.WriteAllTextAsync( - filePath, - $""" - #!/bin/bash - cd {workingDir} - {scriptFileName} {scriptArgs} - """, - ct); - - var startInfo = new ProcessStartInfo - { - FileName = "script", - WorkingDirectory = workingDir, - RedirectStandardInput = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - }; - - var argumentList = RuntimeInformation.IsOSPlatform(OSPlatform.OSX) - ? new[] { "-q", "/dev/null", "su", "-m", username, "-c", filePath } - : new[] { "-qec", "su", "-m", username, "-c", filePath, "/dev/null" }; - - startInfo.ArgumentList.AddRange(argumentList); - - startInfo.Environment.Clear(); - foreach (var kvp in environmentVariables) - { - startInfo.Environment[kvp.Key] = kvp.Value; - } - - GrantRunAsAccess(workingDir); - - var process = Process.Start(startInfo)!; - - // TODO: Should just wait as long as it takes to read "Password:" below - await Task.Delay(1000, ct).WaitAsync(ct); - - // Parse password prompt so consuming code can ignore this initial password check. - var passwordReq = "Password:".Length; - var buff = new char[passwordReq]; - await process.StandardOutput.ReadAsync(buff, 0, passwordReq); - var message = new string(buff); - if (message != "Password:") - { - throw new Exception($"Unexpected startup message: {message}"); - } - - await process.StandardInput.WriteLineAsync(runAs!.Password); - if (process.StandardOutput.Read() != '\r' || process.StandardOutput.Read() != '\n') - { - throw new Exception("Expecting new line"); - } - - return process; - } - - [SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Only invoked on the macOS/Linux run-as path")] - static void GrantRunAsAccess(string workingDir) - { - var dirMode = UnixFileMode.UserRead - | UnixFileMode.UserWrite - | UnixFileMode.UserExecute - | UnixFileMode.GroupRead - | UnixFileMode.GroupWrite - | UnixFileMode.GroupExecute - | UnixFileMode.OtherRead - | UnixFileMode.OtherWrite - | UnixFileMode.OtherExecute; - - var fileMode = UnixFileMode.UserRead - | UnixFileMode.UserWrite - | UnixFileMode.UserExecute - | UnixFileMode.GroupRead - | UnixFileMode.GroupWrite - | UnixFileMode.GroupExecute - | UnixFileMode.OtherRead - | UnixFileMode.OtherWrite - | UnixFileMode.OtherExecute; - - // The sandbox policy must stay readable but never writable by the run-as user or co-tenants. - var policyMode = UnixFileMode.UserRead | UnixFileMode.GroupRead | UnixFileMode.OtherRead; - - new DirectoryInfo(workingDir).UnixFileMode = dirMode; - foreach (var dir in Directory.EnumerateDirectories(workingDir, "*", SearchOption.AllDirectories)) - { - new DirectoryInfo(dir).UnixFileMode = dirMode; - } - - foreach (var file in Directory.EnumerateFiles(workingDir, "*", SearchOption.AllDirectories)) - { - File.SetUnixFileMode(file, fileMode); - } - - var claudeDir = Path.Combine(workingDir, ".claude"); - if (Directory.Exists(claudeDir)) - { - foreach (var policyFile in Directory.EnumerateFiles(claudeDir, "*.json", SearchOption.TopDirectoryOnly)) - { - File.SetUnixFileMode(policyFile, policyMode); - } - } + return Process.Start(startInfo)!; } } \ No newline at end of file diff --git a/source/Calamari.AiAgent/ClaudeCodeBehaviour/InvokeClaudeCodeBehaviour.cs b/source/Calamari.AiAgent/ClaudeCodeBehaviour/InvokeClaudeCodeBehaviour.cs index 77a4f478e..24ea51fdf 100644 --- a/source/Calamari.AiAgent/ClaudeCodeBehaviour/InvokeClaudeCodeBehaviour.cs +++ b/source/Calamari.AiAgent/ClaudeCodeBehaviour/InvokeClaudeCodeBehaviour.cs @@ -42,8 +42,6 @@ public async Task Execute(RunningDeployment context) if (string.IsNullOrWhiteSpace(apiToken)) throw new CommandException($"Variable '{SpecialVariables.Action.Claude.ApiToken}' is required but was not provided."); - var runAs = BuildRunAs(variables); - var argsBuilder = new ClaudeCommandArgsBuilder().WithPrompt(prompt); var model = variables.Get(SpecialVariables.Action.Claude.Model); @@ -135,7 +133,6 @@ public async Task Execute(RunningDeployment context) var response = await new ClaudeCodeCliRunner(log).RunAsync( argsBuilder, environment, - runAs, workingDir, context.CurrentDirectory, cancellationToken.Token); @@ -181,17 +178,4 @@ static SandboxMode ResolveSandboxMode(IVariables variables) throw new CommandException($"Unknown value '{raw}' for '{SpecialVariables.Action.Claude.SandboxMode}'. Expected one of: None, Bash, SandboxRuntime."); } - - static ProcessCredentials? BuildRunAs(IVariables variables) - { - var username = variables.Get(SpecialVariables.Action.Claude.RunAsUsername); - if (string.IsNullOrWhiteSpace(username)) - return null; - - return new ProcessCredentials - { - Username = username, - Password = variables.Get(SpecialVariables.Action.Claude.RunAsPassword), - }; - } } \ No newline at end of file diff --git a/source/Calamari.AiAgent/SpecialVariables.cs b/source/Calamari.AiAgent/SpecialVariables.cs index 3c281c9bb..4cc97dd39 100644 --- a/source/Calamari.AiAgent/SpecialVariables.cs +++ b/source/Calamari.AiAgent/SpecialVariables.cs @@ -25,8 +25,6 @@ public static class Claude public const string PermissionMode = "Octopus.Action.Claude.PermissionMode"; public const string Effort = "Octopus.Action.Claude.Effort"; public const string PassEnvironmentVariables = "Octopus.Action.Claude.PassEnvironmentVariables"; - public const string RunAsUsername = "Octopus.Action.Claude.RunAsUsername"; - public const string RunAsPassword = "Octopus.Action.Claude.RunAsPassword"; public const string SandboxMode = "Octopus.Action.Claude.SandboxMode"; public const string SandboxSettings = "Octopus.Action.Claude.SandboxSettings";