Skip to content

Add Claude Code AI Agent step with CLI harness and Linux user impersonation#1996

Draft
zentron wants to merge 23 commits into
mainfrom
robe/poc-aiagent
Draft

Add Claude Code AI Agent step with CLI harness and Linux user impersonation#1996
zentron wants to merge 23 commits into
mainfrom
robe/poc-aiagent

Conversation

@zentron

@zentron zentron commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

⚠️ Does this change require a corresponding Server Change?
⚠️ If so - please add a "Requires Server Change" label to this PR!

Requires Server Change

Background

Adds the Calamari side of the Claude Code Step — a new step type that runs Anthropic's Claude Code CLI as a non-interactive agent during deployments and runbooks. The step streams structured JSON output back to the task log, supports user-configured MCP servers and custom skills, and runs under an impersonated user context for isolation.

Results

flowchart TD
    A[RunAgentCommand] --> B[Setup working directory<br/>skills, MCP config, system prompt]
    B --> C{Platform?}
    C -->|Windows| D[Native ProcessStartInfo<br/>user impersonation]
    C -->|Linux / macOS| E["Wrapper script + script(1) / su(1)<br/>password via stdin"]
    D --> F[claude CLI — stream-json mode]
    E --> F
    F --> G[Stream processor<br/>task log, service messages, artifacts]
Loading
  • New Calamari.AiAgent project with RunAgentCommand entry point
  • Linux/macOS impersonation uses script(1) + su(1) with a wrapper shell script to avoid nested escaping. Windows uses native ProcessStartInfo.UserName.
  • build.sh fix: replaced perl JSON parser with grep/sed — Amazon Linux 2 container dropped perl

Resolves

How to review

  • Start with ClaudeCodeProcessStartInfo.cs — the most nuanced file. Handles the platform split and script/su wrapper script approach. macOS and Linux script commands have different argument ordering.
  • ClaudeCodeStreamProcessor.cs + stream models — JSON deserialization of Claude's stream-json output.
  • ClaudeCodeCliRunner.cs — orchestrator that wires up the process, streams stdout/stderr, and collects artifacts.
  • The writer classes and ClaudeCommandArgsBuilder are straightforward — skim these.

Caveats

  • The script/su approach reads a Password: prompt from stdout before forwarding to the stream processor — fragile if su changes its prompt format. Acceptable for POC.
  • Recursive directory permissions for the working directory when running as another user still needs finalising.

@gitguardian

gitguardian Bot commented Jun 9, 2026

Copy link
Copy Markdown

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

zentron and others added 16 commits June 10, 2026 10:15
Introduce provider selection (Anthropic/OpenAI) via variables, add
Microsoft.Extensions.AI.OpenAI bridge package, and bump MEAI packages
to 10.5.0 for ModelContextProtocol 1.3.0 compatibility.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ce messages

Introduce InvokeClaudeCodeBehaviour as an alternative to the SDK-based
provider, shelling out to `claude -p` with stream-json output. Includes
typed stream event models, a dedicated stream processor, temp working
directory with skills support, and an ai-agent-usage service message
for reporting cost/token metrics back to the server.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add --strict-mcp-config with configurable MCP servers passed via
ClaudeCodeOptions. Wire up GitHub and Octopus MCP servers from
variables in InvokeClaudeCodeBehaviour.

Introduce enums for StreamEventType and ContentBlockType used in
processor routing, but keep model properties as strings to avoid
JsonSerializer throwing on unknown values from the evolving CLI format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ProcessCredentials and RunAs support to ClaudeCodeCliRunner.
Windows uses ProcessStartInfo.UserName/PasswordInClearText natively.
Linux wraps in sudo -u <user> -- env ANTHROPIC_API_KEY=<key> claude <args>
to avoid -E requiring SETENV in sudoers (see ADR-001).

Add unit tests for stream processor (17 tests) and CLI runner (10 tests).
Replace integration test fixture with clean validation and Claude Code tests.
Fix malformed JSON handling and result event fallback in stream processor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…uilder

Replace hardcoded GitHubToken MCP config with a generic JSON-encoded
McpServers variable that supports user-configured MCP servers. The
Octopus MCP server remains hardcoded. Add McpServerEntry record for
deserialization. Update stream models with full init/hook/result fields
from Claude Code stream-json output. Extract CLI argument construction
into ClaudeCommandArgsBuilder with max-turns and max-budget-usd support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d fixes

Write filtered deployment variables to deployment-variables.json in the
working directory so the agent can read deployment context on demand.
Add --effort flag support (low/medium/high/xhigh/max). Auto-add
mcp__<server>__* to --allowedTools for all configured MCP servers so
they aren't blocked by --permission-mode dontAsk. Add --bare flag for
clean environment isolation. Fix verbose log missing newlines between
JSON events. Update PermissionDenial model to structured record. Remove
superpowers planning docs from repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduces a customEnvVars dictionary for env vars that need to be
explicitly set on the process (currently just ANTHROPIC_API_KEY).
No behaviour change — prepares for passing these vars into ApplyCredentials
so they can be inlined into a su -c command on Linux in a follow-up task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a static ShellQuote method to ClaudeCodeCliRunner that wraps a
value in single quotes and escapes any embedded single quotes as '\''.
This is needed to safely embed values (API keys, env var values) into
su -c command strings without shell injection risk.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tion

On Linux, instead of using ProcessStartInfo.UserName (which requires CAP_SETUID),
wrap the command with `script -qec "su - {user} -c '{envVars} {cmd}'" /dev/null`
to allocate a pseudo-TTY for su. Custom env vars are inlined into the command
string since `su -` starts a login shell that clears the environment.
Windows behaviour is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ate ADR comment

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@zentron zentron force-pushed the robe/poc-aiagent branch from 5313cf9 to c344557 Compare June 10, 2026 00:15
@zentron zentron changed the title Robe/poc aiagent Add Claude Code AI Agent step with CLI harness and Linux user impersonation Jun 14, 2026

@eddymoulton eddymoulton left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Comments to discuss - we didn't look at the tests

Comment thread docs/superpowers/plans/2026-06-08-linux-su-impersonation.md Outdated
Comment thread source/Calamari.AiAgent/AgentBehaviour/InvokeAgentBehaviour.cs Outdated
Comment thread source/Calamari.AiAgent/ClaudeCodeBehaviour/ClaudeCodeCliRunner.cs Outdated
Comment on lines +92 to +98
var username = runAs?.Username!;
if (runAs == null || string.IsNullOrEmpty(username))
{
var startInfo1 = StartSimpleProcess(workingDir, argsBuilder, environmentVariables);
var process1 = Process.Start(startInfo1)!;
return process1;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should StartMacOrLinuxProcess just be this (ie. remove runAs support) for this PR?

Then when it's confirmed working we can add the functionality in and have it tested.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

given the whole thing is feature flagged and still in development im not too concerned about this one at the moment.

runAs,
argsBuilder,
environmentVariables,
ct);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Have looked into separate classes for windows and mac/linux instead of this if (platform) here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I did at one point, but felt simple enough to just combine into one. Open to using OO if we add enough additional complexity (or improves testing) that we think warrants.

Comment thread source/Calamari.AiAgent/ClaudeCodeBehaviour/SkillsWriter.cs Outdated

foreach (var skill in userSkills)
{
var dirName = SanitizeFileName(skill.Name);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should we put these skills in directories that aren't named by the skill name to avoid a lot of this sanitization work?

Comment thread source/Calamari.AiAgent/Calamari.AiAgent.csproj Outdated
Comment thread source/Calamari.AiAgent/RunAgentCommand.cs Outdated
Comment thread source/Calamari.AiAgent/SpecialVariables.cs Outdated
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