Skip to content

Harden CLI wrappers: logging, safety, robustness, architecture tests#2

Merged
ClankerGuru merged 1 commit into
mainfrom
hardening
Apr 10, 2026
Merged

Harden CLI wrappers: logging, safety, robustness, architecture tests#2
ClankerGuru merged 1 commit into
mainfrom
hardening

Conversation

@ClankerGuru

@ClankerGuru ClankerGuru commented Apr 9, 2026

Copy link
Copy Markdown
Owner

Summary

Full hardening pass across all 5 modules.

Anti-patterns fixed

  • printlnlogger.lifecycle() in ClaudeMcpServeTask, OpenCodeServeTask
  • System.err.printlogger.warn() in Cli.kt
  • try-catchrunCatching in Cli.exec

Safety

  • Dangerous flags (--dangerously-skip-permissions, --dangerously-bypass-approvals-and-sandbox, --yolo) now emit logger.warn() when enabled
  • Cli.which() pre-check before execution — fails fast with "not found. Install it first."

Robustness

  • Fixed potential deadlock in Cli.exec: stdout/stderr now read concurrently on separate threads
  • timeoutSeconds configurable per agent via extension property
  • Timeout expiration test added

Architecture

  • Extracted duplicated addFlag helper to exec module CommandBuilder.kt
  • ForbiddenPatternTest (konsist slopTest) added to all 5 modules
  • README property names corrected (mcpName, pluginName)

Test plan

  • All 141 tasks pass
  • slopTest passes in all modules
  • Timeout expiration test validates destroyForcibly path
  • Coverage still above 90%

Summary by CodeRabbit

  • Documentation

    • Updated Gradle task property names in configuration examples for MCP server and plugin management.
  • New Features

    • Added configurable timeout settings for CLI execution across all agent modules.
  • Bug Fixes

    • Enhanced logging for unsafe operations and improved CLI error messages.
  • Tests

    • Added code quality enforcement tests across modules.

- Replace println/System.err with Gradle logger across all modules
- Replace try-catch with runCatching in Cli.exec
- Fix README property names (mcpName, pluginName)
- Warn on dangerous flags (dangerouslySkipPermissions, dangerouslyBypass, yolo)
- Fix Cli.exec deadlock: read stdout/stderr concurrently with waitFor
- Add Cli.which() pre-check: fail fast with helpful message if binary missing
- Extract addFlag helper to exec module CommandBuilder.kt
- Add timeoutSeconds to all agent extensions, wired through execAndPrint
- Add ForbiddenPatternTest (konsist slopTest) to all 5 modules
- Add timeout expiration test for Cli.exec
@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR standardizes CLI execution across agent modules by adding configurable timeouts to task extensions, refactoring the shared addFlag utility, improving logging via Gradle loggers, and enforcing code quality through forbidden-pattern tests. README documentation is corrected to use proper property names.

Changes

Cohort / File(s) Summary
Documentation Updates
claude/README.md, codex/README.md, copilot/README.md
Updated Gradle property flags from -Pname to -PmcpName or -PpluginName in command examples.
Settings Extension Enhancements
claude/src/main/kotlin/.../Claude.kt, codex/src/main/kotlin/.../Codex.kt, copilot/src/main/kotlin/.../Copilot.kt, opencode/src/main/kotlin/.../OpenCode.kt
Added timeoutSeconds: Long property to each SettingsExtension, defaulting to CliRequest.DEFAULT_TIMEOUT_SECONDS.
Task Execution & Logging
claude/src/main/kotlin/.../ClaudeRunTask.kt, claude/src/main/kotlin/.../ClaudeMcpServeTask.kt, codex/src/main/kotlin/.../CodexExecTask.kt, copilot/src/main/kotlin/.../CopilotRunTask.kt, opencode/src/main/kotlin/.../OpenCodeRunTask.kt, opencode/src/main/kotlin/.../OpenCodeServeTask.kt
Integrated timeoutSeconds parameter into Cli.execAndPrint() calls; replaced local addFlag helpers with shared implementation; added warning logs for dangerous settings; replaced println() with logger.lifecycle() where appropriate.
Core Execution Utilities
exec/src/main/kotlin/.../Cli.kt
Refactored Cli.exec() to use runCatching instead of IOException catching; added concurrent thread reading for stdout/stderr; enhanced Cli.execAndPrint() with timeoutSeconds parameter and pre-flight which() check; changed Cli.which() return type from Boolean to String?.
Shared Command Builder
exec/src/main/kotlin/.../CommandBuilder.kt
Introduced two addFlag() extension functions on MutableList<String> for conditional flag/value appending, replacing local implementations across agent modules.
Code Quality Enforcement
claude/src/slopTest/..., codex/src/slopTest/..., copilot/src/slopTest/..., exec/src/slopTest/..., opencode/src/slopTest/.../ForbiddenPatternTest.kt
Added Kotest BehaviorSpec tests across modules using Konsist to enforce: no println( calls, no System.out/err usage, and no wildcard imports.
Execution Test Updates
exec/src/test/kotlin/.../CliTest.kt
Updated assertions for Cli.execAndPrint() error messages; added timeout scenario test for Cli.exec(); updated Cli.which() assertions from Boolean to nullable String expectations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Hops with glee through timeout bounds!
Shared flags now hop through exec grounds,
Loggers dance where println stood,
Forbidden patterns understood! 🎯✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly reflects the main objectives: hardening CLI wrappers through logging improvements, safety enhancements, robustness fixes, and architecture tests—all clearly evident in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
opencode/src/slopTest/kotlin/zone/clanker/agents/opencode/ForbiddenPatternTest.kt (1)

13-27: Strengthen forbidden-call detection to avoid trivial bypasses.

contains("println(") misses variants like println (. Using regex keeps the guardrail effective without changing test intent.

🔧 Suggested hardening
 class ForbiddenPatternTest :
     BehaviorSpec({
         given("main source set") {
             val scope = Konsist.scopeFromProduction(moduleName = "opencode")
+            val printlnPattern = Regex("""\bprintln\s*\(""")
+            val systemErrPattern = Regex("""\bSystem\.err\b""")
+            val systemOutPattern = Regex("""\bSystem\.out\b""")
 
             then("no println calls") {
                 scope.files.assertFalse {
-                    it.text.contains("println(")
+                    printlnPattern.containsMatchIn(it.text)
                 }
             }
 
             then("no System.err usage") {
                 scope.files.assertFalse {
-                    it.text.contains("System.err")
+                    systemErrPattern.containsMatchIn(it.text)
                 }
             }
 
             then("no System.out usage") {
                 scope.files.assertFalse {
-                    it.text.contains("System.out")
+                    systemOutPattern.containsMatchIn(it.text)
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@opencode/src/slopTest/kotlin/zone/clanker/agents/opencode/ForbiddenPatternTest.kt`
around lines 13 - 27, The test currently uses fragile substring checks
(it.text.contains("println("), "System.err", "System.out") that can be bypassed
by spacing; update the assertions in ForbiddenPatternTest.kt (the
scope.files.assertFalse { it.text... } lambdas) to use regex-based matching
instead — e.g. replace the println check with a word-boundary/whitespace-aware
regex like \bprintln\s*\( and replace System.err/System.out checks with regexes
that allow optional whitespace around the dot (e.g. System\s*\.\s*(err|out)) so
the assertions use containsMatch/Regex matching on it.text rather than plain
contains.
exec/src/slopTest/kotlin/zone/clanker/agents/exec/ForbiddenPatternTest.kt (1)

12-28: Text-based pattern matching may produce false positives.

The substring checks like it.text.contains("println(") will match occurrences in comments, string literals, or identifiers (e.g., myPrintln(). Consider using Konsist's AST-aware APIs or regex with word boundaries for more precise detection.

That said, for a "slop test" intended to catch obvious anti-patterns, the current approach is pragmatic and likely sufficient for your use case.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exec/src/slopTest/kotlin/zone/clanker/agents/exec/ForbiddenPatternTest.kt`
around lines 12 - 28, The current tests in ForbiddenPatternTest.kt use naive
substring checks (scope.files.assertFalse { it.text.contains("println(") },
it.text.contains("System.err"), it.text.contains("System.out")) which can yield
false positives from comments, strings, or identifiers; update these assertions
to use a more precise matcher such as Konsist's AST-aware APIs to find call
expressions or field accesses (e.g., search for call expressions named "println"
or property accesses "System.out"/"System.err"), or at minimum switch to regex
with word boundaries (e.g., \bprintln\b and \bSystem\.out\b) to avoid matching
inside other tokens; modify the assertions around scope.files to iterate parsed
AST nodes or apply the regex so the test only flags real usage rather than
substrings.
exec/src/main/kotlin/zone/clanker/agents/exec/Cli.kt (1)

91-92: Consider using logger.lifecycle() instead of print() for stdout.

Line 91 uses print(result.stdout) while line 92 uses logger.warn(result.stderr). For consistency with the PR's goal of replacing print statements with Gradle logger calls, consider using logger.lifecycle(result.stdout) for stdout output as well.

♻️ Suggested change
-        print(result.stdout)
-        if (result.stderr.isNotEmpty()) logger.warn(result.stderr)
+        if (result.stdout.isNotEmpty()) logger.lifecycle(result.stdout)
+        if (result.stderr.isNotEmpty()) logger.warn(result.stderr)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@exec/src/main/kotlin/zone/clanker/agents/exec/Cli.kt` around lines 91 - 92,
Replace the direct stdout print call with the Gradle logger lifecycle method for
consistency: instead of print(result.stdout) in Cli.kt, call
logger.lifecycle(result.stdout) (optionally guarded by
result.stdout.isNotEmpty()) and keep the existing logger.warn(result.stderr) for
stderr; update the code around the result variable where stdout/stderr are
handled to use logger.lifecycle for standard output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@exec/src/main/kotlin/zone/clanker/agents/exec/Cli.kt`:
- Around line 91-92: Replace the direct stdout print call with the Gradle logger
lifecycle method for consistency: instead of print(result.stdout) in Cli.kt,
call logger.lifecycle(result.stdout) (optionally guarded by
result.stdout.isNotEmpty()) and keep the existing logger.warn(result.stderr) for
stderr; update the code around the result variable where stdout/stderr are
handled to use logger.lifecycle for standard output.

In `@exec/src/slopTest/kotlin/zone/clanker/agents/exec/ForbiddenPatternTest.kt`:
- Around line 12-28: The current tests in ForbiddenPatternTest.kt use naive
substring checks (scope.files.assertFalse { it.text.contains("println(") },
it.text.contains("System.err"), it.text.contains("System.out")) which can yield
false positives from comments, strings, or identifiers; update these assertions
to use a more precise matcher such as Konsist's AST-aware APIs to find call
expressions or field accesses (e.g., search for call expressions named "println"
or property accesses "System.out"/"System.err"), or at minimum switch to regex
with word boundaries (e.g., \bprintln\b and \bSystem\.out\b) to avoid matching
inside other tokens; modify the assertions around scope.files to iterate parsed
AST nodes or apply the regex so the test only flags real usage rather than
substrings.

In
`@opencode/src/slopTest/kotlin/zone/clanker/agents/opencode/ForbiddenPatternTest.kt`:
- Around line 13-27: The test currently uses fragile substring checks
(it.text.contains("println("), "System.err", "System.out") that can be bypassed
by spacing; update the assertions in ForbiddenPatternTest.kt (the
scope.files.assertFalse { it.text... } lambdas) to use regex-based matching
instead — e.g. replace the println check with a word-boundary/whitespace-aware
regex like \bprintln\s*\( and replace System.err/System.out checks with regexes
that allow optional whitespace around the dot (e.g. System\s*\.\s*(err|out)) so
the assertions use containsMatch/Regex matching on it.text rather than plain
contains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d6a40cf-3619-4ffc-bcd7-cc0c97608d0e

📥 Commits

Reviewing files that changed from the base of the PR and between 20cb2bf and cef38c3.

📒 Files selected for processing (21)
  • claude/README.md
  • claude/src/main/kotlin/zone/clanker/agents/claude/Claude.kt
  • claude/src/main/kotlin/zone/clanker/agents/claude/ClaudeMcpServeTask.kt
  • claude/src/main/kotlin/zone/clanker/agents/claude/ClaudeRunTask.kt
  • claude/src/slopTest/kotlin/zone/clanker/agents/claude/ForbiddenPatternTest.kt
  • codex/README.md
  • codex/src/main/kotlin/zone/clanker/agents/codex/Codex.kt
  • codex/src/main/kotlin/zone/clanker/agents/codex/CodexExecTask.kt
  • codex/src/slopTest/kotlin/zone/clanker/agents/codex/ForbiddenPatternTest.kt
  • copilot/README.md
  • copilot/src/main/kotlin/zone/clanker/agents/copilot/Copilot.kt
  • copilot/src/main/kotlin/zone/clanker/agents/copilot/CopilotRunTask.kt
  • copilot/src/slopTest/kotlin/zone/clanker/agents/copilot/ForbiddenPatternTest.kt
  • exec/src/main/kotlin/zone/clanker/agents/exec/Cli.kt
  • exec/src/main/kotlin/zone/clanker/agents/exec/CommandBuilder.kt
  • exec/src/slopTest/kotlin/zone/clanker/agents/exec/ForbiddenPatternTest.kt
  • exec/src/test/kotlin/zone/clanker/agents/exec/CliTest.kt
  • opencode/src/main/kotlin/zone/clanker/agents/opencode/OpenCode.kt
  • opencode/src/main/kotlin/zone/clanker/agents/opencode/OpenCodeRunTask.kt
  • opencode/src/main/kotlin/zone/clanker/agents/opencode/OpenCodeServeTask.kt
  • opencode/src/slopTest/kotlin/zone/clanker/agents/opencode/ForbiddenPatternTest.kt

@ClankerGuru ClankerGuru merged commit c2ec8d2 into main Apr 10, 2026
2 checks passed
@ClankerGuru ClankerGuru deleted the hardening branch April 10, 2026 01:03
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.

1 participant