Skip to content

fix: align command condition expansion with shell commands#2076

Merged
yottahmd merged 4 commits intomainfrom
fix-precondition-command-expansion
Apr 30, 2026
Merged

fix: align command condition expansion with shell commands#2076
yottahmd merged 4 commits intomainfrom
fix-precondition-command-expansion

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Apr 30, 2026

Summary

  • align command-form condition evaluation with shell command expansion semantics
  • share runtime command eval options between the command executor and condition execution paths
  • add regression coverage for home-relative DAG env vars in preconditions and repeat-policy conditions

Root Cause

Command-form conditions were evaluated with eval.OnlyReplaceVars(), which deferred scoped variables to the shell. That diverged from shell command: steps, where Dagu expands the command text before execution. Values like ~/... therefore worked in shell commands but failed in conditions because tilde expansion does not happen after shell parameter expansion.

Testing

  • go test ./internal/runtime -run "TestEvalConditions|TestRunner_DAGPreconditions|TestRunner_RepeatPolicy" -count=1
  • go test ./internal/intg -run "TestPreconditionWithDAGEnvVars|TestDAGLevelPreconditionWithEnvVars|TestPreconditionWithHomeRelativeDAGEnvVar|TestRepeatPolicy" -count=1
  • go test ./internal/runtime/builtin/command -run TestCommandExecutor_GetEvalOptions -count=1

Closes #2057

Summary by CodeRabbit

  • Documentation

    • Updated OpenAPI schema documentation for condition execution semantics, clarifying behavior when the expected parameter is omitted versus specified.
  • Tests

    • Added integration tests for condition evaluation with environment variable expansion and repeat policy.
    • Enhanced unit tests with improved capability registration lifecycle management.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9daf5c11-c4eb-4914-a6b0-4816e3e73128

📥 Commits

Reviewing files that changed from the base of the PR and between 02bf754 and 0a771e2.

📒 Files selected for processing (2)
  • internal/core/capabilities_test.go
  • internal/runtime/builtin/command/command.go
✅ Files skipped from review due to trivial changes (1)
  • internal/core/capabilities_test.go

📝 Walkthrough

Walkthrough

The pull request fixes variable expansion inconsistencies in preconditions by centralizing command evaluation options logic. It updates OpenAPI schema documentation for Condition properties, introduces a new CommandEvalOptions function to standardize shell-backed command string evaluation, refactors condition evaluation to use this function, and adds integration and unit tests to verify home-relative path expansion in preconditions and repeat policies.

Changes

Cohort / File(s) Summary
API Schema Documentation
api/v1/api.yaml
Updated Condition.condition and Condition.expected property descriptions to clarify execution semantics: command checks via shell variable expansion when expected is omitted; string comparison when expected is set.
Runtime Evaluation Logic
internal/runtime/eval_options.go
New CommandEvalOptions(shell []string) function that computes eval options based on shell type: enables OS expansion for direct/empty shells; uses WithoutDollarEscape with conditional WithoutExpandEnv for Unix-like and nix shells; preserves env expansion for other shells (e.g., PowerShell).
Command/Condition Evaluation
internal/runtime/builtin/command/command.go, internal/runtime/condition.go
Refactored to delegate shell-to-eval-option logic to CommandEvalOptions(shell) instead of local decisions; removed inline shell expansion rules and eval.OnlyReplaceVars() usage.
Integration Tests
internal/intg/precondition_env_test.go, internal/intg/repeat_test.go
Added POSIX-specific tests (skipped on Windows) verifying home-relative paths (~/...) in environment variables are correctly expanded in precondition command checks and repeat policy conditions.
Unit Tests
internal/runtime/condition_test.go, internal/core/capabilities_test.go
New unit test for EvalConditions verifying home-relative path expansion in command-form preconditions; test cleanup adding per-subtest capability registration teardown to prevent cross-test leakage.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • dagucloud/dagu#2072: Modifies evaluation option construction and usage to control substitution/expansion semantics in variable handling.
  • dagucloud/dagu#2075: Introduces field-specific eval-option hooks (CommandEvalOptions/ScriptEvalOptions) and refactors multiple call sites to determine command/script evaluation behavior.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: aligning command condition expansion behavior with shell commands to ensure consistent variable and tilde expansion semantics.
Linked Issues check ✅ Passed The PR successfully addresses all objectives from issue #2057: eliminates expansion inconsistency via CommandEvalOptions, ensures home-relative paths work in preconditions/repeat-policy, and adds regression test coverage for the exact user scenario.
Out of Scope Changes check ✅ Passed All changes are directly related to aligning condition expansion semantics with command expansion: schema documentation updates, new eval_options logic, condition evaluation refactoring, and regression tests for the specific use case.

✏️ 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 fix-precondition-command-expansion

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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

@yottahmd
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/core/capabilities_test.go (1)

93-99: ⚡ Quick win

Add cleanup for globally registered capabilities in subtests.

Each subtest registers into a global registry without unregistering. Adding t.Cleanup(...) after each registration keeps tests isolated and prevents cross-test state bleed.

Suggested patch
 		RegisterExecutorCapabilities("command-eval-opts-test", ExecutorCapabilities{
 			Command: true,
 			GetCommandEvalOptions: func(_ context.Context, _ Step) []eval.Option {
 				return []eval.Option{eval.WithoutExpandShell()}
 			},
 		})
+		t.Cleanup(func() { UnregisterExecutorCapabilities("command-eval-opts-test") })

 		RegisterExecutorCapabilities("script-eval-opts-test", ExecutorCapabilities{
 			Command: true,
 			Script:  true,
 			GetScriptEvalOptions: func(_ context.Context, _ Step) []eval.Option {
 				return []eval.Option{eval.WithNoExpansion()}
 			},
 		})
+		t.Cleanup(func() { UnregisterExecutorCapabilities("script-eval-opts-test") })

 		RegisterExecutorCapabilities("config-eval-opts-test", ExecutorCapabilities{
 			Command: true,
 		})
+		t.Cleanup(func() { UnregisterExecutorCapabilities("config-eval-opts-test") })

 		RegisterExecutorCapabilities("legacy-eval-opts-test", ExecutorCapabilities{
 			Command: true,
 			Script:  true,
 			GetEvalOptions: func(_ context.Context, _ Step) []eval.Option {
 				return []eval.Option{eval.WithoutExpandShell()}
 			},
 		})
+		t.Cleanup(func() { UnregisterExecutorCapabilities("legacy-eval-opts-test") })

Also applies to: 107-114, 122-125, 132-139

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

In `@internal/core/capabilities_test.go` around lines 93 - 99, Each subtest calls
RegisterExecutorCapabilities (e.g.,
RegisterExecutorCapabilities("command-eval-opts-test",
ExecutorCapabilities{...})) but never unregisters, so add t.Cleanup calls
immediately after each RegisterExecutorCapabilities call to remove the entry
when the subtest ends; specifically, after each
RegisterExecutorCapabilities("...") add t.Cleanup(func(){
UnregisterExecutorCapabilities("...") }) (use the same registration key string)
so tests don’t leak global state.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/core/capabilities_test.go`:
- Around line 93-99: Each subtest calls RegisterExecutorCapabilities (e.g.,
RegisterExecutorCapabilities("command-eval-opts-test",
ExecutorCapabilities{...})) but never unregisters, so add t.Cleanup calls
immediately after each RegisterExecutorCapabilities call to remove the entry
when the subtest ends; specifically, after each
RegisterExecutorCapabilities("...") add t.Cleanup(func(){
UnregisterExecutorCapabilities("...") }) (use the same registration key string)
so tests don’t leak global state.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4a9ec84f-908c-41b4-bb44-0aa7251ba9f2

📥 Commits

Reviewing files that changed from the base of the PR and between 939a9d5 and 02bf754.

📒 Files selected for processing (3)
  • internal/core/capabilities.go
  • internal/core/capabilities_test.go
  • internal/runtime/builtin/command/command.go

@yottahmd
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@yottahmd yottahmd merged commit 0df19b5 into main Apr 30, 2026
10 checks passed
@yottahmd yottahmd deleted the fix-precondition-command-expansion branch April 30, 2026 12:37
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.

Inconsistent behaviour between commands and preconditions

1 participant