Skip to content

Add engine.permission-mode as a first-class Claude engine config field#33658

Closed
Copilot wants to merge 5 commits into
mainfrom
copilot/decouple-engine-permission-mode
Closed

Add engine.permission-mode as a first-class Claude engine config field#33658
Copilot wants to merge 5 commits into
mainfrom
copilot/decouple-engine-permission-mode

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented May 21, 2026

The network egress firewall injects bash: ["*"], which triggered the hasBashWildcardInTools heuristic and silently forced --permission-mode bypassPermissions, making --allowed-tools enforcement ineffective. The only override was a fragile engine.args workaround relying on Claude CLI's last-flag-wins behavior.

Changes

  • EngineConfig struct — adds PermissionMode string field; extracted from frontmatter engine.permission-mode in both standard and inline-definition code paths
  • ValidationvalidateEnginePermissionMode rejects any value outside {auto, acceptEdits, plan, bypassPermissions}; wired into both compiler orchestrator paths
  • claude_engine.go — explicit permission-mode takes priority over the bash-wildcard auto-selection; fallback behavior is unchanged
  • Schemapermission-mode added with an enum constraint to engine_config variants 1 and 2 in main_workflow_schema.json; autocomplete-data.json regenerated

Usage

engine:
  id: claude
  permission-mode: auto   # auto | acceptEdits | plan | bypassPermissions

Setting permission-mode: auto (or acceptEdits) with the firewall enabled now keeps --allowed-tools enforcement active, decoupling tool-boundary policy from the firewall's bash wildcard injection.

Copilot AI and others added 2 commits May 21, 2026 00:53
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…de engine

- Add PermissionMode field to EngineConfig struct
- Extract permission-mode from frontmatter in ExtractEngineConfig (both standard and inline-definition paths)
- Add validateEnginePermissionMode that accepts: auto, acceptEdits, plan, bypassPermissions
- Wire validation in compiler_orchestrator_workflow.go and compiler_string_api.go
- Update GetExecutionSteps in claude_engine.go to honor explicit permission-mode before falling back to bash-wildcard logic
- Add permission-mode to main_workflow_schema.json (engine_config oneOf variants 1 and 2)
- Regenerate docs/public/editor/autocomplete-data.json
- Add tests for extraction, validation, and Claude engine override behavior

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Decouple engine.permission-mode from bash-wildcard auto-selection Add engine.permission-mode as a first-class Claude engine config field May 21, 2026
Copilot AI requested a review from pelikhan May 21, 2026 01:04
@pelikhan pelikhan marked this pull request as ready for review May 21, 2026 03:23
Copilot AI review requested due to automatic review settings May 21, 2026 03:23
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Design Decision Gate 🏗️ failed during design decision gate check.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧪 Test Quality Sentinel completed test quality analysis.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧠 Matt Pocock Skills Reviewer failed during the skills-based review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

PR Code Quality Reviewer completed the code quality review.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧠 Matt Pocock Skills Reviewer failed during the skills-based review.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds engine.permission-mode as a first-class workflow frontmatter field (Claude-only) so users can explicitly control Claude Code CLI’s --permission-mode without relying on engine.args ordering, restoring effective --allowed-tools enforcement when the firewall injects bash: ["*"].

Changes:

  • Adds EngineConfig.PermissionMode, extracts it from frontmatter for both standard and inline engine definitions, and validates it against the allowed enum.
  • Updates Claude execution-step generation to prefer explicit engine.permission-mode over the bash-wildcard heuristic defaulting.
  • Extends the JSON schema + editor autocomplete data to include engine.permission-mode.
Show a summary per file
File Description
pkg/workflow/engine.go Adds PermissionMode to EngineConfig and extracts engine.permission-mode from frontmatter (inline + non-inline).
pkg/workflow/engine_validation.go Adds validateEnginePermissionMode to enforce allowed values.
pkg/workflow/engine_validation_test.go Adds validation tests for engine.permission-mode (currently needs gofmt/goimports).
pkg/workflow/engine_config_test.go Adds extraction tests for engine.permission-mode (currently needs gofmt/goimports).
pkg/workflow/compiler_string_api.go Wires permission-mode validation into the string parsing path.
pkg/workflow/compiler_orchestrator_workflow.go Wires permission-mode validation into the file parsing path.
pkg/workflow/claude_engine.go Prioritizes explicit permission-mode over bash-wildcard heuristic when building Claude CLI args.
pkg/workflow/claude_engine_test.go Adds tests ensuring explicit permission-mode overrides heuristic selection.
pkg/parser/schemas/main_workflow_schema.json Adds engine.permission-mode enum to engine config schema variants; includes large reformat due to regen.
docs/public/editor/autocomplete-data.json Regenerated autocomplete data to include engine.permission-mode (also includes other unrelated changes).

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 9/10 changed files
  • Comments generated: 4

Comment on lines +677 to +762
tests := []struct {
name string
workflow *WorkflowData
expectError bool
errorSubstr string
}{
{
name: "nil workflow data",
workflow: nil,
expectError: false,
},
{
name: "nil engine config",
workflow: &WorkflowData{},
expectError: false,
},
{
name: "permission-mode not set — no error",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude"},
},
expectError: false,
},
{
name: "valid mode: auto",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "auto"},
},
expectError: false,
},
{
name: "valid mode: acceptEdits",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "acceptEdits"},
},
expectError: false,
},
{
name: "valid mode: plan",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "plan"},
},
expectError: false,
},
{
name: "valid mode: bypassPermissions",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "bypassPermissions"},
},
expectError: false,
},
{
name: "invalid mode: unknown value",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "superuser"},
},
expectError: true,
errorSubstr: "invalid value",
},
{
name: "invalid mode: case-mismatch",
workflow: &WorkflowData{
EngineConfig: &EngineConfig{ID: "claude", PermissionMode: "bypasspermissions"},
},
expectError: true,
errorSubstr: "invalid value",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
compiler := NewCompiler()
err := compiler.validateEnginePermissionMode(tt.workflow)

if tt.expectError {
require.Error(t, err, "Expected validation error")
if tt.errorSubstr != "" {
assert.Contains(t, err.Error(), tt.errorSubstr, "Expected error substring mismatch")
}
return
}

assert.NoError(t, err, "Expected permission-mode validation to pass")
})
}
}
Comment on lines +1140 to +1209
compiler := NewCompiler()

tests := []struct {
name string
frontmatter map[string]any
expectedMode string
}{
{
name: "permission-mode auto",
frontmatter: map[string]any{
"engine": map[string]any{
"id": "claude",
"permission-mode": "auto",
},
},
expectedMode: "auto",
},
{
name: "permission-mode acceptEdits",
frontmatter: map[string]any{
"engine": map[string]any{
"id": "claude",
"permission-mode": "acceptEdits",
},
},
expectedMode: "acceptEdits",
},
{
name: "permission-mode plan",
frontmatter: map[string]any{
"engine": map[string]any{
"id": "claude",
"permission-mode": "plan",
},
},
expectedMode: "plan",
},
{
name: "permission-mode bypassPermissions",
frontmatter: map[string]any{
"engine": map[string]any{
"id": "claude",
"permission-mode": "bypassPermissions",
},
},
expectedMode: "bypassPermissions",
},
{
name: "permission-mode not set — empty string",
frontmatter: map[string]any{
"engine": map[string]any{
"id": "claude",
},
},
expectedMode: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, config := compiler.ExtractEngineConfig(tt.frontmatter)
if config == nil {
t.Fatal("Expected non-nil config")
}
if config.PermissionMode != tt.expectedMode {
t.Errorf("PermissionMode = %q, want %q", config.PermissionMode, tt.expectedMode)
}
})
}
}
Comment on lines +87 to +91
"pull_request_reviewer": {
"type": "null|string",
"desc": "Experimental synthetic reviewer lifecycle trigger.",
"leaf": true
},
Comment on lines +187 to 199
// An explicit engine.permission-mode config field overrides both defaults.
var permissionMode string
if workflowData.EngineConfig != nil && workflowData.EngineConfig.PermissionMode != "" {
permissionMode = workflowData.EngineConfig.PermissionMode
claudeLog.Printf("Using explicit permission mode from config: %s", permissionMode)
} else if hasBashWildcardInTools(workflowData.Tools) {
claudeLog.Print("Unrestricted bash detected: using bypassPermissions mode")
permissionMode = "bypassPermissions"
} else {
permissionMode = "acceptEdits"
}
claudeArgs = append(claudeArgs, "--permission-mode", permissionMode)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Design Decision Gate 🏗️ failed during design decision gate check.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review Complete ✅

This PR successfully adds engine.permission-mode as a first-class configuration field for the Claude engine. The implementation is clean, well-tested, and properly integrated.

Strengths:

  • Consistent extraction in both inline and non-inline engine paths
  • Proper validation with helpful error messages
  • Smart defaulting: explicit config → bash wildcard heuristic → safe default
  • Comprehensive test coverage

No issues found — ready to merge.

🔎 Code quality review by PR Code Quality Reviewer · ● 8.1M

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

PR Code Quality Reviewer completed the code quality review.

@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 90/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All tests validate behavioral contracts with comprehensive error and edge case coverage.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

🧪 Test Quality Sentinel completed test quality analysis.

Draft generated by Design Decision Gate from PR #33658 diff.
Author must review and finalize before merge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

Review Summary

This PR successfully adds engine.permission-mode as a first-class config field with proper extraction, validation, and schema updates. The implementation is well-tested and follows established patterns.

Critical Issue Found:

  • Args ordering bug: The explicit permission-mode can still be overridden by engine.args due to flag ordering (custom args are appended after the explicit flag). This undermines the goal of providing deterministic precedence.

Already Noted (from previous reviews):

  • Test formatting issues in engine_validation_test.go and engine_config_test.go (will fail CI)
  • Unrelated autocomplete changes in autocomplete-data.json

What's Done Well:
✅ Clean validation with helpful error messages
✅ Comprehensive test coverage for both extraction and validation
✅ Proper schema updates with enum constraints
✅ Good documentation in comments

Please address the args ordering issue to ensure the explicit config has true precedence over any engine.args override.

🔎 Code quality review by PR Code Quality Reviewer · ● 4.3M

Comments that could not be inline-anchored

pkg/workflow/claude_engine.go:214

The explicit engine.permission-mode is set at line 198, but engine.args are appended at line 214 after the permission-mode flag. This means a user can still override the explicit setting via engine.args: [&quot;--permission-mode&quot;, &quot;bypassPermissions&quot;], reintroducing the last-flag-wins fragility this PR aims to eliminate.

Suggested fix: Either:

  1. Append --permission-mode after custom args (move lines 198 to after line 215), or
  2. Filter/remove any --permission-mode from `Engin…

@pelikhan pelikhan closed this May 21, 2026
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 70/100

⚠️ Acceptable, with suggestions

Metric Value
New/modified tests analyzed 3
✅ Design tests (behavioral contracts) 3 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 1 (33%)
Duplicate test clusters 0
Test inflation detected YES (all 3 files: 4.0-6.0:1 ratios)
🚨 Coding-guideline violations 0 (minor: missing assertion messages in 1 test)

Test Classification Details

Test File Classification Issues Detected
TestClaudeEnginePermissionModeExplicitOverride pkg/workflow/claude_engine_test.go:271 ✅ Design Happy-path only (no error cases)
TestEnginePermissionModeExtraction pkg/workflow/engine_config_test.go:1139 ✅ Design Missing assertion messages (uses stdlib), limited edge cases
TestValidateEnginePermissionMode pkg/workflow/engine_validation_test.go:676 ✅ Design Good error coverage

📊 Detailed Test Analysis

TestClaudeEnginePermissionModeExplicitOverride

Classification: Design test (high value)
What design invariant does this test enforce? Explicit engine.permission-mode config overrides bash-wildcard inference defaults.
What would break if deleted? Users setting explicit permission-mode values would have them silently ignored, breaking a key feature.
Assertions: 2 (require.Len, assert.Contains with messages)
Table-driven: Yes (4 cases: auto, acceptEdits, bypassPermissions, plan)
Error coverage: No

Suggestion: Add error/edge cases:

  • Invalid permission mode value
  • Nil EngineConfig
  • Malformed tools config

TestEnginePermissionModeExtraction

Classification: Design test (high value)
What design invariant does this test enforce? YAML frontmatter engine.permission-mode field correctly extracted into EngineConfig struct.
What would break if deleted? Permission-mode values in workflow files would get silently dropped or misinterpreted.
Assertions: 2 (t.Fatal, t.Errorf)
Table-driven: Yes (5 cases: auto, acceptEdits, plan, bypassPermissions, not set)
Error coverage: Yes (checks for nil config)

Issues detected:

  • ⚠️ Uses stdlib t.Fatal/t.Errorf instead of testify assertions with descriptive messages (guideline preference)
  • Limited edge case coverage (missing: type mismatches, null values, malformed YAML)

Suggestion: Convert to testify assertions:

require.NotNil(t, config, "Expected ExtractEngineConfig to return non-nil config")
assert.Equal(t, tt.expectedMode, config.PermissionMode, "PermissionMode should match expected value")

TestValidateEnginePermissionMode

Classification: Design test (high value)
What design invariant does this test enforce? Validation function rejects invalid permission-mode values and accepts valid ones.
What would break if deleted? Invalid permission-mode values could pass validation and cause runtime failures or security issues.
Assertions: 3 (require.Error, assert.Contains, assert.NoError with messages)
Table-driven: Yes (9 cases: nil workflow, nil config, empty string, 4 valid modes, 2 invalid modes)
Error coverage: Yes (2 invalid cases)

Strengths:

  • ✅ Excellent error coverage (7 valid + 2 invalid cases)
  • ✅ Good edge cases (nil workflow, nil config, case-mismatch)
  • ✅ Descriptive assertion messages

Potentially missing: Type mismatch edge cases (e.g., permission-mode as int/array), though this may be caught upstream.


Test Inflation Analysis

⚠️ All three test files show inflation ratios exceeding the 2:1 guideline:

Test File Production File Test Lines Prod Lines Ratio
claude_engine_test.go claude_engine.go +54 +9 6.0:1
engine_config_test.go engine.go +72 +18 4.0:1
engine_validation_test.go engine_validation.go +89 +20 4.45:1

Why this matters: High test-to-production ratios can indicate:

  • Table-driven tests with many cases (legitimate)
  • Excessive setup boilerplate (refactorable)
  • Duplicate test scenarios (consolidatable)

In this PR: The inflation is primarily due to table-driven tests with comprehensive test cases (4-9 cases per function). This is the preferred pattern in this codebase and represents good coverage rather than wasteful duplication. However, the ratio is notably high.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 3 tests — all unit tests (//go:build !integration)

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%).

Score breakdown:

  • Behavioral coverage: 40/40 pts (100% design tests)
  • Error/edge case coverage: 10/30 pts (33% have error cases)
  • Low duplication: 20/20 pts (no duplicates)
  • Proportional growth: 0/10 pts (test inflation penalty)

Recommendations:

  1. Add error cases to TestClaudeEnginePermissionModeExplicitOverride (nil EngineConfig, invalid mode, malformed tools)
  2. Convert stdlib assertions in TestEnginePermissionModeExtraction to testify for consistency
  3. Consider edge cases for type mismatches in extraction/validation tests

Despite the inflation ratio, the tests provide strong behavioral coverage of the new permission-mode feature. The table-driven pattern with multiple cases is appropriate and follows project conventions.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

🧪 Test quality analysis by Test Quality Sentinel · ● 14.1M ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 70/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%). All three new tests verify behavioral contracts. See detailed analysis in the comment above.

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.

Decouple engine.permission-mode from bash-wildcard auto-selection

3 participants