Skip to content

[AI-68] Hosted Codex agents on macOS and Linux (CLI/daemon) - #71

Merged
alexeyzimarev merged 37 commits into
mainfrom
worktree-logical-spinning-nest
May 15, 2026
Merged

alexeyzimarev merged 37 commits into
mainfrom
worktree-logical-spinning-nest

Conversation

@alexeyzimarev

Copy link
Copy Markdown
Member

Summary

  • Adds vendor: codex support to the daemon's hosted-agent path. The dashboard can now launch a Codex CLI under PTY supervision with the same overlay / pre-trust / permission-bridge guarantees Claude already has on macOS and Linux.
  • Refactors AgentOrchestrator to dispatch through an IHostedAgentLauncher strategy (ClaudeLauncher + CodexLauncher). Claude behavior is preserved; Codex is new.
  • Splits LocalPermissionBridge URLs by vendor (/{token}/{vendor}/permission-request) with vendor-shaped responses; Codex hook bounces fail-closed through the daemon.

Scope: CLI / daemon repo only. Paired server-repo PR in kapacitor-server (new required Vendor field on DaemonCommands.LaunchAgentCommand, 5-arg RequestPermission, 6-arg PermissionRequested broadcast, ClaudeCodePermissionExtension.Vendor, dashboard vendor selector, vendor-aware permission UI) ships together — staging is single-controlled and the components upgrade in lockstep. No back-compat shims.

Design + plan: docs/superpowers/specs/2026-05-14-ai-68-codex-hosted-agents-design.md and docs/superpowers/plans/2026-05-14-ai-68-codex-hosted-agents.md.

Follow-ups: AI-632 (PR review for Codex), AI-633 (sandbox/approval selector in launch dialog), AI-72 (Windows hosted Codex).

Test plan

  • Verify CI green
  • Manual smoke on macOS: launch a hosted Codex agent against a real worktree, run a multi-turn session with a tool call + permission prompt, verify terminal renders and permission round-trips through the dashboard
  • Manual smoke on Linux: same as macOS
  • Negative case: on a machine with no `~/.codex/hooks.json`, verify the daemon emits the actionable `LaunchFailed` message instructing the user to run `kapacitor plugin install --codex`
  • Project-scope hooks case: install hooks with `kapacitor plugin install --codex --project` in a source repo, launch a hosted Codex agent from that repo, verify the overlay copies the hooks into the worktree and the preflight passes
  • Hosted-Claude regression: launch a Claude agent and confirm the new `/claude/permission-request` URL works end-to-end (permission prompt round-trips)
  • Confirm AOT publish still clean on both `kapacitor.csproj` and `Kapacitor.Daemon.csproj` after merge

Key changes

Wire format

  • `LaunchAgentCommand.Vendor` and `AgentRunStarted.Vendor` — required, no default
  • `ServerConnection.RequestPermissionAsync` gains required `string vendor` parameter, invokes 5-arg server hub

Strategy abstraction

  • `IHostedAgentLauncher` + `LauncherContext` + `LaunchArgs`
  • `ClaudeLauncher` extracted from `AgentOrchestrator` (behavior-preserving)
  • `CodexLauncher` new: `--sandbox workspace-write` + `--ask-for-approval on-request`, effort `max` → `xhigh`, `--no-alt-screen`
  • `FileSystemOverlay` shared helper

Codex pre-trust

  • `CodexConfigWriter` writes `[projects.""] trust_level = "trusted"` to `~/.codex/config.toml` via Tomlyn source-gen context (AOT-clean)
  • `CodexHooksParser` moved to `Kapacitor.Core` for daemon-side reuse
  • `CodexHooksNotInstalledException` is a typed fail-fast preflight; orchestrator catches and emits actionable `LaunchFailed`
  • `CodexPaths` switched to computed properties so tests can scope HOME

Permission bridge

  • URL split: `POST /{token}/{vendor}/permission-request`; legacy unprefixed URL 404s
  • Codex response strips `applyPermissions` / `updatedInput` (which Codex cannot honor)
  • `DaemonBridgeUrl.TryParseLoopback` extracted as shared loopback-validation helper

CLI hooks

  • Claude `permission-request` now posts to `/claude/permission-request`
  • Codex `codex-hook PermissionRequest` gains daemon-bridge bounce branch (fail-closed: deny + exit 1 on any HTTP/loopback failure). Outside daemon context the legacy allow-stub is preserved.

Config / DI

  • `DaemonConfig.CodexPath` (default `"codex"`)
  • `DaemonRunner` registers both launchers + vendor-keyed dictionary

Docs

  • README "Hosted Codex agents" section + daemon-config table; `help-codex-hook.txt` notes daemon-bridge behavior

Tests

  • 631 unit + 9 integration tests, all pass
  • AOT clean on both CLI and daemon publishes

alexeyzimarev and others added 30 commits May 14, 2026 16:57
Captures the strategy-pattern split of AgentOrchestrator into per-vendor
IHostedAgentLauncher impls, the Tomlyn-based ~/.codex/config.toml pre-trust
writer, LocalPermissionBridge per-vendor URL routing, and the CLI codex-hook
PermissionRequest bridge bounce. Includes the confirmed Codex pre-trust
spike findings. Out-of-scope follow-ups tracked in AI-632 (PR review) and
AI-633 (sandbox/approval selection).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Six follow-ups from review on /Users/alexey/.../design.md (file:line refs):

- Add §3.5 covering the kapacitor-server wire-contract changes (DaemonCommands,
  CapacitorHub, LaunchAgentDialog, AgentRunStarted read-model) so AI-68 is
  scoped as a single cross-repo issue with a paired-PR rollout ordering.
- §4.3 CodexLauncher.Prepare gains a fail-fast hook preflight that throws
  CodexHooksNotInstalledException when ~/.codex/hooks.json doesn't reference
  kapacitor codex-hook for SessionStart / Stop / PermissionRequest — keeps
  hosted Codex from silently running without session linking, watcher, or
  permission round-trip.
- §4.4 adds AgentInstance.Vendor and routes the failed-launch cleanup path
  (orchestrator line 361) through launcher.Cleanup instead of inlined
  Claude-specific calls.
- §5.2 switches CodexPaths.Home from init-once to a computed property so
  HOME overrides take effect mid-test, and creates the parent .codex
  directory before atomic write so first-time Codex users get pre-trusted.
- §7.3 Codex CLI bridge bounce becomes fail-closed (deny + nonzero exit on
  any HTTP failure), with loopback validation extracted from
  PermissionRequestCommand.cs:70 as a shared DaemonBridgeUrl helper.
- §8 test list updated for the new fail-closed semantics, the directory
  creation case, the hook preflight, and the failed-after-prepare cleanup
  regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five follow-ups + the open question from the second review pass:

- §7.0 (new) explicitly updates PermissionRequestCommand.cs:63 to post to
  /claude/permission-request when the bridge URL split lands. Adds the
  hosted-Claude regression test under §8.3 so the URL migration cannot
  break Claude silently.
- §3.5 server rollout: RequestPermission gains an OPTIONAL fifth `vendor`
  parameter (defaulted to "claude") instead of a required one. SignalR's
  positional-arg dispatch lets old daemons (four-arg call sites) keep
  working against the new server. Server-first ordering is now safe.
  LaunchAgentCommand.Vendor on the server side also defaults to "claude"
  for old-daemon JSON-deserialisation compatibility.
- §4.3 hook preflight accepts either user-scope (~/.codex/hooks.json) OR
  project-scope (worktree/.codex/hooks.json after overlay) installs.
  Overlay runs BEFORE preflight so project hooks copied from the source
  repo satisfy the check. Splits hooks across the two scopes are also
  accepted in aggregate.
- §4.3 + §10 move EntryReferencesKapacitorCodexHook + CodexHookEvents +
  hooks.json parsing helpers to new src/Kapacitor.Core/CodexHooksParser.cs
  so the daemon can use them (Kapacitor.Daemon only references Core, not
  the CLI project).
- §8.8 AOT publish covers BOTH src/kapacitor/kapacitor.csproj AND
  src/Kapacitor.Daemon/Kapacitor.Daemon.csproj. Daemon is where Tomlyn
  lands so its publish has to be the one that's grepped for IL3050/IL2026.
- Open question resolved: §3.5 + §10.2 pull permission-request UI vendor
  awareness into AI-68 scope. PendingPermission carries Vendor;
  PermissionRequestCard hides Always Allow + Update Input for Codex.
  Server tests added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four follow-ups from third review:

- §3.5 + §6.2 + §10.2 switch from "optional fifth parameter on
  RequestPermission" to "new RequestPermissionV2 hub method with the same
  four args plus vendor". This repo's existing SignalR regression notes
  (SendTranscriptBatchWireTests.cs:15, RemoteAgentService.cs:142) document
  that HubConnection.InvokeAsync dispatches by exact arity and does NOT
  backfill optional values. The named-method approach is the only safe
  pattern. Old daemons keep calling four-arg RequestPermission; new
  daemons invoke V2. Wire-level tests added.
- §3.5 expands permission-UI vendor awareness beyond PermissionRequestCard
  to cover AgentChatView.razor:62 ("Yes, always" inline option) and
  ElicitationOption.cs:13 (Claude-only "always" variant). Vendor threads
  through InterruptIssued, PendingPermission, SignalR PermissionRequested
  push, AgentChatContext, and AgentChatView. Server-side tests added for
  each surface.
- §6.2 updates the daemon's ServerConnection.RequestPermissionAsync
  signature to take string vendor and to invoke "RequestPermissionV2".
  New LocalPermissionBridgeTests cover that POST /{token}/codex/...
  reaches the server with vendor = "codex" and the claude path with
  vendor = "claude".
- §5.4 reconciles the "best-effort vs fail-fast" semantics that previously
  contradicted §4.3. Scopes the swallow-and-continue policy to filesystem
  / TOML errors only; CodexHooksNotInstalledException stays fail-fast and
  produces LaunchFailed. Documents the wrapper shape with explicit catch
  ordering so future fail-fast preconditions follow the same pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four follow-ups from fourth review:

- PermissionRequested SignalR broadcast hits the same arity trap as
  RequestPermission. Spec switches to a dual-broadcast: keep the existing
  five-arg PermissionRequested unchanged for old clients, add a sibling
  six-arg PermissionRequestedV2 that includes vendor. RemoteAgentService
  subscribes to both. New wire-regression tests cover both shapes firing
  on the same trigger.
- InterruptIssued comes from the external Kurrent.Agent.Schema package
  and is not editable here. Vendor is carried on Capacitor-owned
  ClaudeCodePermissionExtension instead (ClaudeCodeExtensions.cs:107).
  §3.5 + §10.2 updated to reflect the extension as the carrier.
- IHostedAgentLauncher.Prepare XML doc rewritten to acknowledge both
  failure modes — filesystem/parse errors swallowed inside the launcher,
  typed preflight exceptions allowed to propagate to LaunchFailed —
  pointing to §5.4's wrapper shape for the orchestrator-side catch
  ordering.
- Permission-elicitation UI surfaces corrected to match the actual code
  shape: PendingPermission lives in ElicitationOption.cs (add Vendor
  field), AgentChatView's "Yes, always" is hardcoded markup (Razor-render
  conditional), AgentChatContext exposes scalar fields (add
  PermissionVendor, not a nested CurrentPermission object). Dropped the
  misnamed ElicitationOption_factory test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reverts the V2 hub method, dual SignalR broadcast, and nullable-default
workarounds added in prior amendments. Staging is the only environment
and the deployment is single-controlled — server, daemon, and clients
upgrade together as part of this Linear issue. The compatibility layers
were carrying rollout-window complexity for a window that does not exist
in practice.

Changes:
- §3.1 LaunchAgentCommand.Vendor is a required positional parameter
  (no C# default). Required positionals moved before optional ones to
  satisfy C# language rules.
- §3.5 RequestPermission bumped to five args (adds vendor); no V2 alias.
- §3.5 PermissionRequested broadcast bumped to six args (adds vendor);
  no V2 alias, no dual-broadcast.
- §3.5 DaemonCommands.LaunchAgentCommand.Vendor required (no
  "claude" default). Old-daemon JSON deserialisation compat language
  removed.
- §6.2 daemon-side ServerConnection.RequestPermissionAsync invokes plain
  RequestPermission (not RequestPermissionV2).
- §10.2 server file list and tests pruned: dropped Old_daemon_simulator,
  PermissionRequested_old_event_still_fires, Server_dual_broadcasts,
  LaunchAgentCommand_deserialises_with_default_vendor. Added
  LaunchAgentCommand_round_trips_required_vendor_field and
  LaunchAgentCommand_rejects_unknown_vendor_at_controller_boundary.

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

21-task TDD plan covering the §10.1 surface — Tomlyn dep + AOT gate,
CodexHooksParser move to Core, CodexPaths computed properties, DaemonBridgeUrl
loopback helper, CodexHooksNotInstalledException, CodexConfigWriter,
LaunchAgentCommand/AgentRunStarted Vendor fields, ServerConnection vendor
plumbing, Claude CLI hook URL migration, IHostedAgentLauncher abstraction,
ClaudeLauncher extraction, AgentInstance.Vendor, orchestrator vendor routing,
DaemonConfig.CodexPath, CodexLauncher (overlay + preflight + pre-trust +
BuildArgs), LocalPermissionBridge per-vendor routing, Codex CLI hook bridge
bounce (fail-closed), DI registration, README updates, final AOT gate.

Cross-repo §10.2 server-side work is explicitly scoped out — paired PR
plan lives in the kapacitor-server worktree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move EntryReferencesKapacitorCodexHook and CodexHookEvents from PluginCommand
into a new CodexHooksParser in Kapacitor.Core so Kapacitor.Daemon can reference
the predicate without taking a dependency on the CLI project. Update all call
sites (PluginCommand, StatusCommand) to delegate to CodexHooksParser.

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

Uses Tomlyn 2.4.0 TomlTable model with AOT-safe GetBuiltInTypeInfo<TomlTable>
via a thin TomlSerializerContext subclass — avoids RequiresDynamicCode/
RequiresUnreferencedCode on the generic Deserialize<T> overload.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add required string Vendor positional to LaunchAgentCommand (between
AttachmentIds and Kind to satisfy C# required-before-optional rule).
Daemon validates at top of HandleLaunchAgent; rejects anything other than
"claude" or "codex" via LaunchFailedAsync. Wire-format test added to
confirm round-trip serialization.

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

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

HandleLaunchAgent now looks up the vendor-specific launcher from a
constructor-injected dictionary, invokes Prepare/BuildArgs/Cleanup,
and surfaces CodexHooksNotInstalledException as an actionable
LaunchFailed. CleanupAgentAsync and the failed-launch catch block
dispatch teardown through the same dictionary, with a NoopPtyProcess
stand-in so the launcher can run without a live PTY. A few
ServerConnection methods become virtual so tests can override them.

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

Switch from flat /{token}/permission-request to /{token}/{vendor}/permission-request.
Claude responses carry the full shape (applyPermissions, updatedInput); Codex responses
emit behavior only. Vendor is forwarded to RequestPermissionAsync replacing the "claude"
stub. Legacy URL now returns 404. FakeServerConnection captures vendor for assertion.

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

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

linear Bot commented May 15, 2026

Copy link
Copy Markdown

AI-68

@qodo-code-review

Copy link
Copy Markdown

Review Summary by Qodo

Add vendor-specific hosted agent launchers for Codex on macOS/Linux with daemon permission bridge

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Adds vendor-specific hosted agent launcher strategy pattern (IHostedAgentLauncher) enabling
  Codex CLI support alongside Claude on macOS and Linux
• Implements CodexLauncher with sandbox (--sandbox workspace-write), approval
  (--ask-for-approval on-request), and hooks preflight validation
• Implements ClaudeLauncher extracted from AgentOrchestrator preserving existing Claude behavior
  with overlay, MCP config, and pre-trust logic
• Adds Codex pre-trust mechanism via CodexConfigWriter writing TOML entries to
  ~/.codex/config.toml (AOT-safe via Tomlyn source-gen)
• Splits LocalPermissionBridge URLs by vendor (/{token}/{vendor}/permission-request) with
  vendor-shaped responses; Codex hook bridges through daemon with fail-closed semantics
• Refactors CodexHookCommand.HandlePermissionRequest to validate loopback URLs and POST to daemon
  bridge when KAPACITOR_DAEMON_URL is set
• Adds Vendor field to LaunchAgentCommand and AgentRunStarted wire format (required, no
  default)
• Moves CodexHooksParser to Kapacitor.Core for shared CLI/daemon access; exports hook event
  constants and validation predicates
• Converts CodexPaths to computed properties enabling test HOME environment variable scoping
• Centralizes package version management via Directory.Packages.props across all projects
• Comprehensive test coverage for vendor routing, launcher dispatch, permission bridge routing,
  hooks preflight, TOML pre-trust, loopback validation, and wire format serialization
• Updates documentation with hosted Codex agent setup, daemon config settings, and
  permission-request hook daemon-bridge behavior
Diagram
flowchart LR
  Dashboard["Dashboard<br/>vendor selector"]
  LaunchCmd["LaunchAgentCommand<br/>+ Vendor field"]
  Orchestrator["AgentOrchestrator<br/>dispatch by vendor"]
  ClaudeLauncher["ClaudeLauncher<br/>overlay + MCP + pre-trust"]
  CodexLauncher["CodexLauncher<br/>sandbox + approval + hooks"]
  PermBridge["LocalPermissionBridge<br/>/{token}/{vendor}/permission-request"]
  CodexHook["CodexHookCommand<br/>daemon-bridge mode"]
  CodexConfig["CodexConfigWriter<br/>TOML pre-trust"]
  
  Dashboard -->|"Vendor"| LaunchCmd
  LaunchCmd -->|"Vendor"| Orchestrator
  Orchestrator -->|"claude"| ClaudeLauncher
  Orchestrator -->|"codex"| CodexLauncher
  ClaudeLauncher -->|"overlay"| PermBridge
  CodexLauncher -->|"hooks preflight"| CodexConfig
  CodexLauncher -->|"permission-request"| CodexHook
  CodexHook -->|"POST"| PermBridge
Loading

Grey Divider

File Changes

1. src/Kapacitor.Daemon/Services/AgentOrchestrator.cs ✨ Enhancement +130/-413

Refactor agent launch to use vendor-specific launcher strategy pattern

• Refactored to use IHostedAgentLauncher strategy pattern, dispatching Claude and Codex launches
 through vendor-specific launchers
• Added Vendor field to AgentInstance record and LaunchAgentCommand validation
• Extracted Claude-specific logic (overlay, MCP config, pre-trust, tool permissions) into
 ClaudeLauncher
• Moved launcher preparation and argument building into strategy methods; cleanup now delegates to
 launcher
• Added NoopPtyProcess stand-in for failed-launch cleanup paths

src/Kapacitor.Daemon/Services/AgentOrchestrator.cs


2. test/kapacitor.Tests.Unit/AgentOrchestratorVendorTests.cs 🧪 Tests +387/-0

Add vendor routing and launcher dispatch unit tests

• New comprehensive test suite covering vendor routing logic in HandleLaunchAgent
• Tests verify unknown vendors emit LaunchFailed without spawning PTY
• Tests confirm Claude and Codex launchers are called correctly based on vendor
• Tests validate Codex review launches are rejected and CodexHooksNotInstalledException surfaces
 as actionable error

test/kapacitor.Tests.Unit/AgentOrchestratorVendorTests.cs


3. src/Kapacitor.Daemon/Services/ClaudeLauncher.cs ✨ Enhancement +378/-0

Extract Claude launcher with pre-trust and overlay logic

• New launcher implementation extracted from AgentOrchestrator for Claude-specific behavior
• Implements IHostedAgentLauncher with Prepare, BuildArgs, and Cleanup methods
• Handles .claude/ overlay, MCP config writing, workspace pre-trust, and tool permission merging
• Moved all Claude JSON manipulation logic (trust, MCP, tool permissions) from orchestrator

src/Kapacitor.Daemon/Services/ClaudeLauncher.cs


View more (38)
4. test/kapacitor.Tests.Unit/LocalPermissionBridgeTests.cs 🧪 Tests +175/-10

Update permission bridge tests for vendor-specific routing

• Updated existing tests to use new vendor-prefixed URLs (/{token}/claude/permission-request)
• Added per-vendor routing tests verifying Claude and Codex paths return correct response shapes
• Tests confirm legacy path without vendor returns 404
• Tests validate Codex response strips applyPermissions and updatedInput fields

test/kapacitor.Tests.Unit/LocalPermissionBridgeTests.cs


5. test/kapacitor.Tests.Unit/CodexHookCommandTests.cs 🧪 Tests +195/-2

Add Codex hook daemon-bridge integration tests

• Added serial test group PermissionRequestGroup to prevent parallel execution of daemon-bridge
 tests
• New tests verify Codex hook command posts to daemon bridge when KAPACITOR_DAEMON_URL is set
• Tests validate fail-closed behavior (deny) on connection errors, non-loopback hosts, and HTTPS
• Tests confirm daemon bridge is not double-posted when both bridge and server hooks exist

test/kapacitor.Tests.Unit/CodexHookCommandTests.cs


6. test/kapacitor.Tests.Unit/CodexLauncherTests.cs 🧪 Tests +250/-0

Add Codex launcher argument and preparation unit tests

• New test suite for CodexLauncher covering argument building and preparation logic
• Tests verify --sandbox workspace-write, --ask-for-approval on-request, and --no-alt-screen
 flags
• Tests confirm effort max maps to xhigh and other efforts pass through unchanged
• Tests validate hooks preflight throws CodexHooksNotInstalledException when hooks missing

test/kapacitor.Tests.Unit/CodexLauncherTests.cs


7. test/kapacitor.Tests.Unit/CodexConfigWriterTests.cs 🧪 Tests +172/-0

Add Codex config writer TOML pre-trust tests

• New test suite for CodexConfigWriter TOML pre-trust logic
• Tests verify initial config.toml creation with [projects."<path>"] trust_level = "trusted"
• Tests confirm concurrent writers serialize safely and malformed configs are skipped
• Tests validate no-op when already trusted and atomic rename leaves no temp files

test/kapacitor.Tests.Unit/CodexConfigWriterTests.cs


8. src/Kapacitor.Daemon/Services/LocalPermissionBridge.cs ✨ Enhancement +41/-9

Implement vendor-specific permission bridge routing and responses

• Changed URL routing from /{token}/permission-request to /{token}/{vendor}/permission-request
• Added vendor extraction and validation; unknown vendors return 404
• Split response building into BuildClaudeResponse and BuildCodexResponse methods
• Codex response omits applyPermissions and updatedInput fields; passes vendor to server

src/Kapacitor.Daemon/Services/LocalPermissionBridge.cs


9. src/Kapacitor.Daemon/Services/CodexLauncher.cs ✨ Enhancement +108/-0

Implement Codex launcher with hooks preflight and pre-trust

• New Codex launcher implementation with --sandbox workspace-write and `--ask-for-approval
 on-request`
• Maps effort max to xhigh for Codex reasoning effort parameter
• Overlays source .codex/ directory and validates hooks preflight (fail-fast on missing)
• Pre-trusts worktree via CodexConfigWriter; ignores tools array (no allowlist concept)

src/Kapacitor.Daemon/Services/CodexLauncher.cs


10. src/Kapacitor.Daemon/Services/ServerConnection.cs ✨ Enhancement +11/-9

Add vendor parameter to permission request and make methods virtual

• Added vendor parameter to RequestPermissionAsync signature (required, no default)
• Made several methods virtual to support test mocking (UpdateRepoPathsAsync,
 AgentRegisteredAsync, etc.)
• Updated hub invocation to pass vendor to server's 5-arg RequestPermission method

src/Kapacitor.Daemon/Services/ServerConnection.cs


11. src/Kapacitor.Daemon/Services/CodexConfigWriter.cs ✨ Enhancement +97/-0

Add Codex config writer for TOML pre-trust entries

• New utility for writing per-worktree pre-trust entries to ~/.codex/config.toml
• Uses Tomlyn source-gen for AOT-safe TOML serialization
• Implements atomic rename to prevent partial writes; serializes concurrent writers with lock
• Tolerates missing or malformed config files; no-op if already trusted

src/Kapacitor.Daemon/Services/CodexConfigWriter.cs


12. src/kapacitor/Commands/PluginCommand.cs Refactoring +4/-30

Move Codex hook event definitions to shared parser

• Moved CodexHookEvents array to CodexHooksParser for shared daemon/CLI access
• Updated hook installation and removal to use CodexHooksParser.CodexHookEvents
• Updated hook entry validation to use CodexHooksParser.EntryReferencesKapacitorCodexHook

src/kapacitor/Commands/PluginCommand.cs


13. src/Kapacitor.Core/CodexPaths.cs ✨ Enhancement +3/-3

Convert CodexPaths to computed properties for test scoping

• Changed Home, Sessions, and UserHooksJson from init-only properties to computed properties
• Allows tests to scope HOME environment variable without stale cached values
• Simplifies Sessions and UserHooksJson to reference Home property

src/Kapacitor.Core/CodexPaths.cs


14. src/kapacitor/Commands/CodexHookCommand.cs ✨ Enhancement +50/-3

Codex permission-request hook bridges through daemon with fail-closed semantics

• Refactors HandlePermissionRequest to branch on KAPACITOR_DAEMON_URL environment variable
• Adds HandlePermissionRequestViaBridge method that validates loopback URLs and POSTs to daemon
 bridge with fail-closed behavior (denies on HTTP errors)
• Adds HandlePermissionRequestStub for legacy allow-stub behavior when daemon URL is not set
• Adds EmitDenyAndExitNonzero helper to emit deny decision and exit with error code

src/kapacitor/Commands/CodexHookCommand.cs


15. test/kapacitor.Tests.Unit/DaemonBridgeUrlTests.cs 🧪 Tests +66/-0

Loopback URL validation tests for daemon bridge security

• New test file with comprehensive validation of DaemonBridgeUrl.TryParseLoopback method
• Tests accept valid http://127.0.0.1 URLs with trailing slash stripping
• Tests reject HTTPS, non-loopback hosts, IPv6, DNS names, malformed URIs, null, and empty inputs

test/kapacitor.Tests.Unit/DaemonBridgeUrlTests.cs


16. test/kapacitor.Tests.Unit/LaunchAgentCommandWireFormatTests.cs 🧪 Tests +39/-1

Wire format tests for vendor field on launch commands

• Adds Vendor field to existing LaunchAgentCommand test cases with value "claude"
• Adds new test Vendor_field_round_trips_through_json_serializer verifying "codex" vendor
 serialization
• Adds new test AgentRunStarted_vendor_serialises_into_json_body verifying vendor appears in JSON
 output

test/kapacitor.Tests.Unit/LaunchAgentCommandWireFormatTests.cs


17. test/kapacitor.Tests.Unit/CodexHooksParserTests.cs 🧪 Tests +62/-0

Codex hooks.json parser validation tests

• New test file validating CodexHooksParser static methods for hooks.json parsing
• Tests EntryReferencesKapacitorCodexHook predicate with matching/non-matching commands and null
 inputs
• Tests HasKapacitorHooksFor aggregation across multiple hook events
• Tests CodexHookEvents constant array contains all six events in canonical order

test/kapacitor.Tests.Unit/CodexHooksParserTests.cs


18. src/Kapacitor.Daemon/Services/IHostedAgentLauncher.cs ✨ Enhancement +56/-0

Vendor-agnostic hosted agent launcher strategy interface

• Defines IHostedAgentLauncher interface for vendor-specific agent launch strategies
• Specifies Vendor, CliPath properties and Prepare, BuildArgs, Cleanup methods
• Defines LauncherContext record carrying agent metadata and launch parameters
• Defines LaunchArgs struct for argv array and optional MCP config path

src/Kapacitor.Daemon/Services/IHostedAgentLauncher.cs


19. src/Kapacitor.Core/CodexHooksParser.cs ✨ Enhancement +64/-0

Shared Codex hooks.json parser for CLI and daemon

• New shared parser for ~/.codex/hooks.json used by both CLI and daemon
• Exports CodexHookEvents constant array with six Codex hook event names
• Implements EntryReferencesKapacitorCodexHook predicate to detect kapacitor hook entries
• Implements HasKapacitorHooksFor to verify all required events have kapacitor hooks

src/Kapacitor.Core/CodexHooksParser.cs


20. src/kapacitor/Commands/PermissionRequestCommand.cs ✨ Enhancement +5/-12

Claude permission-request URL migration to vendor-prefixed path

• Updates Claude permission-request bridge URL from "/permission-request" to
 "/claude/permission-request"
• Refactors TryGetLoopbackDaemonUrl to delegate to new DaemonBridgeUrl.TryParseLoopback helper
• Removes inline loopback validation logic in favor of centralized helper

src/kapacitor/Commands/PermissionRequestCommand.cs


21. test/kapacitor.Tests.Unit/CodexPathsHomeIsolationTests.cs 🧪 Tests +54/-0

CodexPaths HOME environment variable isolation tests

• New test file verifying CodexPaths properties reflect current HOME environment variable
• Tests Home, Sessions, and UserHooksJson properties with scoped HOME overrides
• Ensures test isolation via [NotInParallel] attribute and proper cleanup

test/kapacitor.Tests.Unit/CodexPathsHomeIsolationTests.cs


22. test/kapacitor.Tests.Unit/PluginCommandCodexTests.cs 🧪 Tests +3/-3

Plugin command tests updated for moved hooks parser

• Updates three test cases to call CodexHooksParser.EntryReferencesKapacitorCodexHook instead of
 PluginCommand method
• Tests now reference the moved parser implementation in Kapacitor.Core

test/kapacitor.Tests.Unit/PluginCommandCodexTests.cs


23. src/kapacitor/Commands/DaemonBridgeUrl.cs ✨ Enhancement +28/-0

Loopback URL validation helper for daemon bridge security

• New helper class DaemonBridgeUrl with TryParseLoopback static method
• Validates KAPACITOR_DAEMON_URL is http://127.0.0.1 loopback with optional port and path
• Rejects HTTPS, non-loopback hosts, and malformed URIs; strips trailing slashes

src/kapacitor/Commands/DaemonBridgeUrl.cs


24. test/kapacitor.Tests.Unit/PermissionRequestCommandTests.cs 🧪 Tests +4/-3

Permission request command test for strict loopback validation

• Updates AcceptsLocalhostHttp test to RejectsLocalhostDnsName with inverted assertions
• Test now verifies DNS name "localhost" is rejected (requires literal 127.0.0.1)

test/kapacitor.Tests.Unit/PermissionRequestCommandTests.cs


25. src/Kapacitor.Daemon/Services/FileSystemOverlay.cs ✨ Enhancement +23/-0

Shared file system overlay helper for vendor settings

• New helper class FileSystemOverlay with OverlayDirectory static method
• Recursively copies files from source to destination without overwriting existing files
• Used by both ClaudeLauncher and CodexLauncher to merge vendor-specific dotfiles

src/Kapacitor.Daemon/Services/FileSystemOverlay.cs


26. src/Kapacitor.Daemon/Services/CodexHooksNotInstalledException.cs Error handling +14/-0

Typed exception for missing Codex hooks preflight check

• New exception type thrown by CodexLauncher preflight when required hooks are missing
• Carries user-facing message instructing to run kapacitor plugin install --codex
• Orchestrator catches this typed exception and emits LaunchFailed with the message

src/Kapacitor.Daemon/Services/CodexHooksNotInstalledException.cs


27. src/Kapacitor.Daemon/DaemonConfig.cs ⚙️ Configuration changes +1/-0

Daemon configuration for Codex CLI binary path

• Adds CodexPath property with default value "codex" for Codex CLI binary path
• Complements existing ClaudePath property for vendor-specific CLI resolution

src/Kapacitor.Daemon/DaemonConfig.cs


28. src/Kapacitor.Daemon/DaemonRunner.cs ✨ Enhancement +8/-0

Dependency injection registration for hosted agent launchers

• Registers ClaudeLauncher and CodexLauncher as singleton IHostedAgentLauncher implementations
• Registers vendor-keyed dictionary mapping vendor strings to launcher instances
• Enables orchestrator to dispatch launches through vendor-specific strategies

src/Kapacitor.Daemon/DaemonRunner.cs


29. src/Kapacitor.Core/Models.cs ✨ Enhancement +3/-1

Vendor field addition to launch command and run-started models

• Adds required Vendor field to LaunchAgentCommand record (no default)
• Adds required Vendor field to AgentRunStarted record
• Fields enable vendor-aware launch routing and server-side persistence

src/Kapacitor.Core/Models.cs


30. src/kapacitor/Commands/StatusCommand.cs ✨ Enhancement +1/-1

Status command updated for moved hooks parser

• Updates IsCodexHooksInstalled method to call
 CodexHooksParser.EntryReferencesKapacitorCodexHook instead of PluginCommand method
• References moved parser implementation in Kapacitor.Core

src/kapacitor/Commands/StatusCommand.cs


31. src/Kapacitor.Core/PathHelpers.cs ✨ Enhancement +1/-1

Home directory resolution with environment variable override

• Updates HomeDirectory property to check HOME environment variable first before falling back to
 UserProfile
• Enables test isolation by allowing HOME override via environment variable

src/Kapacitor.Core/PathHelpers.cs


32. docs/superpowers/specs/2026-05-14-ai-68-codex-hosted-agents-design.md 📝 Documentation +775/-0

AI-68 hosted Codex agents design specification document

• Comprehensive design specification for hosted Codex agents on macOS and Linux
• Documents architecture, wire format changes, launcher interface, pre-trust mechanism, permission
 bridge routing
• Specifies test coverage, file touch list, and acceptance criteria
• Includes detailed implementation guidance for both CLI and daemon repositories

docs/superpowers/specs/2026-05-14-ai-68-codex-hosted-agents-design.md


33. README.md 📝 Documentation +24/-2

README documentation for hosted Codex agents and daemon config

• Updates Codex setup instructions to mention daemon-hosted agents in addition to local sessions
• Adds new "Hosted Codex agents" section documenting hook installation and sandbox/approval settings
• Adds "Daemon config settings" table documenting ClaudePath and CodexPath configuration options
• Updates agent daemon description to mention vendor selection from dashboard

README.md


34. src/kapacitor/kapacitor.csproj ⚙️ Configuration changes +4/-4

CLI project package version centralization

• Removes explicit version pins from package references (IdentityModel.OidcClient, SignalR.Client,
 Extensions.Http, Spectre.Console)
• Delegates version management to centralized Directory.Packages.props

src/kapacitor/kapacitor.csproj


35. src/Kapacitor.Daemon/Kapacitor.Daemon.csproj Dependencies +4/-3

Daemon project package version centralization and Tomlyn dependency

• Removes explicit version pins from package references (SignalR.Client, Extensions.Hosting,
 Extensions.Http)
• Adds Tomlyn package reference for TOML parsing (no version pin, managed centrally)
• Delegates version management to centralized Directory.Packages.props

src/Kapacitor.Daemon/Kapacitor.Daemon.csproj


36. src/Kapacitor.Core/Resources/help-codex-hook.txt 📝 Documentation +14/-1

Codex hook help text for daemon-bridge permission requests

• Updates PermissionRequest hook documentation to reference daemon-bridge mode
• Adds new "Daemon-bridge mode" section explaining KAPACITOR_DAEMON_URL behavior
• Documents fail-closed semantics and legacy allow-stub fallback for non-daemon contexts

src/Kapacitor.Core/Resources/help-codex-hook.txt


37. test/kapacitor.Tests.Unit/kapacitor.Tests.Unit.csproj Dependencies +4/-3

Unit test project package version centralization and Tomlyn dependency

• Removes explicit version pins from package references (TUnit, WireMock.Net, NSubstitute)
• Adds Tomlyn package reference for TOML parsing in tests
• Delegates version management to centralized Directory.Packages.props

test/kapacitor.Tests.Unit/kapacitor.Tests.Unit.csproj


38. Directory.Packages.props ⚙️ Configuration changes +17/-0

Centralized package version management configuration

• New file establishing centralized package version management across all projects
• Defines versions for all dependencies including new Tomlyn package (2.4.0)
• Enables consistent versioning and simplified maintenance across CLI, daemon, and test projects

Directory.Packages.props


39. test/kapacitor.Tests.Integration/kapacitor.Tests.Integration.csproj ⚙️ Configuration changes +2/-2

Integration test project package version centralization

• Removes explicit version pins from package references (TUnit, WireMock.Net)
• Delegates version management to centralized Directory.Packages.props

test/kapacitor.Tests.Integration/kapacitor.Tests.Integration.csproj


40. Directory.Build.props ⚙️ Configuration changes +1/-1

Build properties package version centralization

• Removes explicit version pin from MinVer package reference
• Delegates version management to centralized Directory.Packages.props

Directory.Build.props


41. docs/superpowers/plans/2026-05-14-ai-68-codex-hosted-agents.md Additional files +3034/-0

...

docs/superpowers/plans/2026-05-14-ai-68-codex-hosted-agents.md


Grey Divider

Qodo Logo

@qodo-code-review

qodo-code-review Bot commented May 15, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (1) 📎 Requirement gaps (0)

Grey Divider


Action required

1. allow = [] JsonArray expression 📘 Rule violation ☼ Reliability
Description
ClaudeLauncher.MergeToolPermissions creates a JsonArray using the collection-expression syntax
[], which can introduce dynamic-code requirements and break NativeAOT/trimming scenarios. This
violates the requirement to use AOT-safe JsonArray construction patterns.
Code

src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[R133-135]

+        if (permissions["allow"] is not JsonArray allow) {
+            allow                = [];
+            permissions["allow"] = allow;
Evidence
PR Compliance ID 2 prohibits using collection-expression syntax for JsonArray. The updated code
assigns allow = [] where allow is a JsonArray, directly triggering the disallowed pattern.

CLAUDE.md
src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[133-135]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`JsonArray` is created via collection-expression syntax (`[]`) in `ClaudeLauncher.MergeToolPermissions`, which is disallowed for AOT compatibility.

## Issue Context
Compliance requires avoiding `JsonArray` collection expressions (e.g. `[]`, `[item1, item2]`) and using constructors like `new JsonArray()` / `new JsonArray(item1, item2)` instead.

## Fix Focus Areas
- src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[133-135]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty HOME breaks paths ✓ Resolved 🐞 Bug ☼ Reliability
Description
PathHelpers.HomeDirectory now prefers $HOME even if it is set to the empty string, which makes
HomeDirectory resolve to "" and downstream paths become relative (e.g., .codex/...,
.claude/...). This can cause the daemon/CLI to read/write config and hook files in the current
working directory instead of the user profile, breaking Codex/Claude preflight and potentially
writing files to unintended locations.
Code

src/Kapacitor.Core/PathHelpers.cs[4]

+    public static string HomeDirectory => Environment.GetEnvironmentVariable("HOME") ?? Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
Evidence
The implementation uses the HOME environment variable without guarding against empty/whitespace;
multiple components then build critical config/hook paths by combining this value with vendor
directories, which becomes relative when HomeDirectory is "".

src/Kapacitor.Core/PathHelpers.cs[1-8]
src/Kapacitor.Core/CodexPaths.cs[3-7]
src/Kapacitor.Core/ClaudePaths.cs[3-9]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PathHelpers.HomeDirectory` uses `Environment.GetEnvironmentVariable("HOME") ?? ...`, which does not treat `HOME=""` as missing. When HOME is the empty string, `HomeDirectory` becomes `""`, causing `Path.Combine(HomeDirectory, ...)` to produce relative paths and misplace reads/writes of `.codex`, `.claude`, and Kapacitor config.

## Issue Context
This value fans out into multiple path roots (Codex and Claude) and is used for both daemon and CLI behavior.

## Fix Focus Areas
- Make `HomeDirectory` fall back when HOME is null/empty/whitespace (and ideally when it’s not rooted).
- Keep behavior stable across macOS/Linux and test environments.

### Recommended code shape
- `var home = Environment.GetEnvironmentVariable("HOME");`
- `if (string.IsNullOrWhiteSpace(home) || !Path.IsPathRooted(home)) home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);`
- `return home;`

## Fix Focus Areas (exact locations)
- src/Kapacitor.Core/PathHelpers.cs[3-6]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Overlay follows symlinks ✓ Resolved 🐞 Bug ⛨ Security
Description
FileSystemOverlay.OverlayDirectory recursively copies files/directories without detecting
symlinks/reparse points, so repo-controlled .claude/ or .codex/ content can cause unbounded
recursion (e.g., symlink loops) or pull content from outside the repo into the worktree. This risks
daemon instability during launch and can unintentionally materialize host-local files into the
worktree the agent runs in.
Code

src/Kapacitor.Daemon/Services/FileSystemOverlay.cs[R11-21]

+    public static void OverlayDirectory(string source, string dest) {
+        Directory.CreateDirectory(dest);
+
+        foreach (var file in Directory.GetFiles(source)) {
+            var destFile = Path.Combine(dest, Path.GetFileName(file));
+            if (!File.Exists(destFile)) File.Copy(file, destFile);
+        }
+
+        foreach (var dir in Directory.GetDirectories(source)) {
+            OverlayDirectory(dir, Path.Combine(dest, Path.GetFileName(dir)));
+        }
Evidence
The overlay helper performs recursive enumeration and copying without any symlink/reparse-point
checks, and it is used to merge .claude and .codex from the source repo into the spawned
worktree during launcher preparation.

src/Kapacitor.Daemon/Services/FileSystemOverlay.cs[10-22]
src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[20-33]
src/Kapacitor.Daemon/Services/CodexLauncher.cs[16-27]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`FileSystemOverlay.OverlayDirectory` recurses through `Directory.GetFiles`/`Directory.GetDirectories` and copies entries without checking for symlinks (or other reparse points). A repo can contain symlinked files/dirs under `.claude`/`.codex`; overlaying these can recurse indefinitely or copy content that lives outside the repo into the worktree.

## Issue Context
This helper is invoked for both vendors during hosted-agent launch preparation.

## Fix Focus Areas
- Prevent recursion into symlink/reparse-point directories.
- Avoid copying symlink targets as regular files.
- Keep behavior “best-effort” (skip unsafe entries rather than failing launch).

### Concrete implementation options
- Use `EnumerationOptions` with `AttributesToSkip = FileAttributes.ReparsePoint` for both files and directories.
- Or, for each entry, detect symlinks via `new FileInfo(path).LinkTarget != null` / `new DirectoryInfo(path).LinkTarget != null` (or `FileAttributes.ReparsePoint`) and `continue`.

## Fix Focus Areas (exact locations)
- src/Kapacitor.Daemon/Services/FileSystemOverlay.cs[11-21]
- src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[20-33]
- src/Kapacitor.Daemon/Services/CodexLauncher.cs[16-27]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment on lines +133 to +135
if (permissions["allow"] is not JsonArray allow) {
allow = [];
permissions["allow"] = allow;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. allow = [] jsonarray expression 📘 Rule violation ☼ Reliability

ClaudeLauncher.MergeToolPermissions creates a JsonArray using the collection-expression syntax
[], which can introduce dynamic-code requirements and break NativeAOT/trimming scenarios. This
violates the requirement to use AOT-safe JsonArray construction patterns.
Agent Prompt
## Issue description
`JsonArray` is created via collection-expression syntax (`[]`) in `ClaudeLauncher.MergeToolPermissions`, which is disallowed for AOT compatibility.

## Issue Context
Compliance requires avoiding `JsonArray` collection expressions (e.g. `[]`, `[item1, item2]`) and using constructors like `new JsonArray()` / `new JsonArray(item1, item2)` instead.

## Fix Focus Areas
- src/Kapacitor.Daemon/Services/ClaudeLauncher.cs[133-135]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/Kapacitor.Core/PathHelpers.cs Outdated
alexeyzimarev and others added 5 commits May 15, 2026 12:27
…ecisions aren't killed at 30s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add claude_path and codex_path as settable daemon profile fields so
users can override the binary paths via `kapacitor config set
daemon.claude_path` / `daemon.codex_path` or via
KAPACITOR_CLAUDE_PATH / KAPACITOR_CODEX_PATH env vars. Previously
DaemonConfig carried these fields but nothing populated them from the
profile — the daemon always resolved "claude" / "codex" via PATH.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…I-635/AI-636 codex hooks)

Conflicts resolved:

- src/Kapacitor.Core/CodexPaths.cs — kept our computed-property form
  from Task 3 (so tests can scope HOME); added main's new UserSkillsDir
  in the same computed form.

- src/Kapacitor.Core/Resources/help-codex-hook.txt — kept the daemon-
  bridge section from AI-68 and reconciled the stub-branch description
  with AI-636's record-and-yield behaviour (no more allow-stub fallback).

- src/Kapacitor.Daemon/DaemonRunner.cs — dropped our redundant
  KAPACITOR_DAEMON_NAME env handling (now subsumed by main's shared
  DaemonNameResolver.Resolve from AI-630). Dropped our OS-username
  fallback for the same reason. Kept our profile-based ClaudePath /
  CodexPath population AND our KAPACITOR_CLAUDE_PATH /
  KAPACITOR_CODEX_PATH env overrides. Kept main's DaemonNameResolver
  call with its arg-validation try/catch.

- src/kapacitor/Commands/CodexHookCommand.cs — kept our dispatcher
  (HandlePermissionRequestStub vs HandlePermissionRequestViaBridge) and
  rewrote the stub branch to match AI-636: bounded 2s POST to
  /hooks/permission-record, emit "{}" so Codex's in-CLI approval prompt
  asks the user. Updated the class-level remarks comment to describe
  both branches.

- src/kapacitor/Commands/PluginCommand.cs — dropped our local
  CodexHookEvents and EntryReferencesKapacitorCodexHook (now in
  CodexHooksParser per Task 2). Kept main's new CodexSkillNames,
  InstallCodexSkills, RemoveCodexSkills, CopyDirectory from AI-629
  (skills install). Kept our PermissionRequestTimeout = 86400 from the
  P1 review fix.

- test/kapacitor.Tests.Unit/CodexHookCommandTests.cs — collapsed our
  PermissionRequestGroup and main's ConsoleSerialGroup into one shared
  ConsoleSerialGroup. Updated every NotInParallel(PermissionRequestGroup)
  attribute to the new name. Took main's record-and-yield assertions for
  the no-daemon-URL case (was: assert allow-stub, now: assert no
  decision) AND kept our env-var isolation pattern. Dropped my old
  PermissionRequest_without_daemon_url_still_uses_legacy_stub test —
  AI-636's PermissionRequest_records_event_and_yields_decision_to_codex
  now covers the stub branch with the correct new contract.

Verified: 719 unit + 9 integration tests green, AOT clean on both
kapacitor.csproj and Kapacitor.Daemon.csproj.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eSystemOverlay against symlinks

Qodo review findings #2 (empty HOME breaks paths) and #3 (overlay follows
symlinks):

- PathHelpers.HomeDirectory now treats HOME as missing when it's empty,
  whitespace, or a non-rooted relative path — falling back to
  Environment.SpecialFolder.UserProfile in all three cases. Previously
  HOME="" or HOME="foo" would silently produce a relative
  HomeDirectory, causing daemon/CLI config reads/writes to land in CWD
  instead of the user profile.

- FileSystemOverlay.OverlayDirectory passes EnumerationOptions
  { AttributesToSkip = FileAttributes.ReparsePoint } to both GetFiles
  and GetDirectories. Symlinks (files and dirs) and other reparse
  points are now skipped during overlay, preventing symlink-loop
  recursion and accidental copy of out-of-repo content into the
  worktree. Both ClaudeLauncher and CodexLauncher use this helper to
  merge .claude/.codex from the source repo into the spawned worktree.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fix in-suite Console.SetOut race

Both tests use Console.SetOut to capture stdout. Stop_maps_to_session_end_codex_route
takes ~3m20s (real WatcherManager path through InlineDrainAsync + WhatsDoneGenerator
spawn against an absent transcript), holding Console.Out captured for the full
duration. With the suite at 727 tests, the long capture window plus TUnit's
parallel scheduling drops captured output for the other ConsoleSerialGroup test
intermittently — both pass in isolation, fail together in suite.

Tightening from NotInParallel(ConsoleSerialGroup) (group-scoped) to plain
NotInParallel (globally sequential) for these two tests guarantees they don't
overlap with any other test, removing the race surface. Other ConsoleSerialGroup
tests in the file (the new PermissionRequest_with_daemon_url_* set from AI-68)
keep their group-scoped attribute — they're fast and serializing only against
each other is sufficient.
@alexeyzimarev
alexeyzimarev merged commit 8056cd0 into main May 15, 2026
4 checks passed
@alexeyzimarev
alexeyzimarev deleted the worktree-logical-spinning-nest branch May 15, 2026 15:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant