Skip to content

fix: prepare tools for local sub-DAG runs - #2452

Merged
yohamta0 merged 2 commits into
mainfrom
agent/fix-subdag-tools
Jul 27, 2026
Merged

fix: prepare tools for local sub-DAG runs#2452
yohamta0 merged 2 commits into
mainfrom
agent/fix-subdag-tools

Conversation

@yohamta0

@yohamta0 yohamta0 commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • prepare tools declared by in-process child DAGs before their runtime agent starts
  • build the child tool environment from the configured base PATH, preserving toolset isolation from the parent DAG
  • cover local child execution with a declared CLI resolved through the injected tool environment

Root cause

The in-process subworkflow refactor in #2248 replaced the local dagu start subprocess with direct agent construction. The subprocess had entered the normal DAG startup path, which prepares declared tools. The new local runner preserved the logic that removes the parent DAG's Aqua environment, but it did not prepare and inject the child DAG's own tool environment.

Official actions execute through this local sub-DAG path, so actions that declare their runtime through tools: could not find that runtime on PATH.

Impact

Child DAGs and official actions now install or reuse their declared toolset and receive the same Aqua environment as root and worker runs.

Closes #2451

Testing


Summary by cubic

Fixes local sub-DAG runs by preparing declared tools before the child agent starts and injecting a clean tool PATH. Child DAGs and official actions can now resolve their tools like root and worker runs.

  • Bug Fixes
    • Build the child tool environment from the configured base PATH (case-insensitive), isolated from the parent.
    • Prepare tools via dagutools.PrepareDAG with the default daguaqua installer; allow override with WithLocalToolInstaller.
    • Append the prepared tool env to agent ExtraEnvs so steps find declared CLIs.
    • Added tests verifying a declared CLI runs via the injected tool env and a custom installer.

Written for commit 7d6d2e2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Local sub-workflows now automatically prepare and expose declared tools to child processes.
    • Added an option to configure the tool installer used by local sub-workflows.
    • Tool lookup now respects the configured environment path, including case-insensitive path settings.
  • Tests

    • Added coverage confirming declared tools are installed and available during local sub-workflow runs.

Copilot AI review requested due to automatic review settings July 27, 2026 07:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The local subflow runner now installs declared child-DAG tools through a configurable installer, derives the tool PATH from the child execution context, and appends prepared tool environment entries to child agents. Tests verify installer invocation and successful tool execution.

Changes

Local sub-DAG tool preparation

Layer / File(s) Summary
Configure and prepare local DAG tools
internal/subflow/local.go, internal/subflow/local_helpers.go
The local runner gains configurable installer support, defaults to the aqua installer, resolves the base PATH, prepares declared DAG tools, and appends their environment entries to child agents.
Validate declared tool execution
internal/subflow/local_test.go
Tests use a platform-specific executable and installer double to verify successful execution, selected package data, invocation count, and configured tools directory.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LocalRunner
  participant PrepareDAG
  participant ToolInstaller
  participant ChildAgent
  LocalRunner->>PrepareDAG: Prepare declared DAG tools
  PrepareDAG->>ToolInstaller: Install configured tool package
  ToolInstaller-->>PrepareDAG: Return installed manifest
  PrepareDAG-->>LocalRunner: Return tool environment entries
  LocalRunner->>ChildAgent: Add entries to ExtraEnvs
Loading

Possibly related PRs

  • dagucloud/dagu#2248: Introduced or wired the in-process local subworkflow runner extended by this change.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes prepare child DAG tools before execution, isolate their PATH, and inject the prepared env, matching #2451's requirements.
Out of Scope Changes check ✅ Passed The modified files and tests are all directly related to fixing tool preparation for local sub-DAG runs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly matches the main change: preparing tools for local sub-DAG runs.
Description check ✅ Passed The description covers summary, root cause, impact, testing, and the linked issue, though the template's Changes and Checklist sections are missing.
✨ 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 agent/fix-subdag-tools

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.

@yohamta0
yohamta0 marked this pull request as ready for review July 27, 2026 07:45

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
internal/subflow/local_test.go (1)

160-207: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the test prove that the injected PATH is required.

The child-tool script always exits successfully, and the test does not constrain the inherited PATH. A host-provided executable could therefore make the test pass even if toolEnvs were not injected. Use a unique executable name or a controlled base PATH, then assert a sentinel/output from that executable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/subflow/local_test.go` around lines 160 - 207, Strengthen
TestLocalRunPreparesDeclaredTools by ensuring child-tool can only be resolved
through the injected toolEnvs PATH: use a unique executable name or constrain
the inherited PATH, and make the script emit a sentinel value. Update the child
DAG command and assertions to verify that sentinel output, while preserving the
existing installer-call and configuration checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/subflow/local.go`:
- Around line 406-416: The prepareDAGTools method currently derives WorkDir from
dag.WorkingDir instead of the request-level directory used by loadInProcessDAG
and SubDAGExecutor.Execute/Rewire. Update its work-directory resolution to use
the resolved request workDir passed through rCtx, and provide that value to
dagutools.InstallOptions so tool installation matches the child agent’s
execution directory.

---

Nitpick comments:
In `@internal/subflow/local_test.go`:
- Around line 160-207: Strengthen TestLocalRunPreparesDeclaredTools by ensuring
child-tool can only be resolved through the injected toolEnvs PATH: use a unique
executable name or constrain the inherited PATH, and make the script emit a
sentinel value. Update the child DAG command and assertions to verify that
sentinel output, while preserving the existing installer-call and configuration
checks.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 552e2c6a-f5c1-4e5a-b33d-ba03a5b56f2a

📥 Commits

Reviewing files that changed from the base of the PR and between 0f49ead and f735f20.

📒 Files selected for processing (3)
  • internal/subflow/local.go
  • internal/subflow/local_helpers.go
  • internal/subflow/local_test.go

Comment thread internal/subflow/local.go Outdated
Comment on lines +406 to +416
func (r *Local) prepareDAGTools(ctx context.Context, rCtx exec.Context, dag *core.DAG) ([]string, error) {
cfg := config.GetConfig(ctx)
workDir := ""
if dag != nil {
workDir = dag.WorkingDir
}
return dagutools.PrepareDAG(ctx, dag, r.toolInstaller, dagutools.InstallOptions{
ToolsDir: cfg.Paths.ToolsDir,
DataDir: cfg.Paths.DataDir,
WorkDir: workDir,
}, dagToolsBasePathForLocalRunner(rCtx))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/sh
rg -n -C3 '\bWorkDir\b' internal/subflow internal/runtime --glob '*.go'

Repository: dagucloud/dagu

Length of output: 7191


🏁 Script executed:

#!/bin/sh
set -eu

echo "== outline internal/subflow/local.go relevant methods =="
ast-grep outline internal/subflow/local.go --view expanded | sed -n '1,220p' || true

echo
echo "== internal/subflow/local.go lines 360-545 =="
sed -n '360,545p' internal/subflow/local.go | nl -ba -v360

echo
echo "== dagutools PrepareDAG signature/usages =="
rg -n -C3 'func PrepareDAG|PrepareDAG\(|type InstallOptions|struct InstallOptions' internal --glob '*.go'

echo
echo "== subworkflow request builders and runParams mappings =="
rg -n -C4 'SubWorkflowRequest|WorkDir|Params|RootDAGRun|ParentDAGRun' internal/runtime/executor internal/runtime/agent internal/subflow --glob '*.go'

Repository: dagucloud/dagu

Length of output: 4081


🏁 Script executed:

#!/bin/sh
set -eu

echo "== internal/subflow/local.go lines 360-575 =="
sed -n '360,575p' internal/subflow/local.go

echo
echo "== dagutools PrepareDAG signature/usages =="
rg -n -C3 'func PrepareDAG|PrepareDAG\(|type InstallOptions|struct InstallOptions' internal --glob '*.go'

echo
echo "== subworkflow request builders and runParams mappings =="
rg -n -C5 'SubWorkflowRequest|WorkDir|Params|RootDAGRun|ParentDAGRun' internal/runtime/executor internal/runtime/agent internal/subflow --glob '*.go'

echo
echo "== executor.SubWorkflowRequest definition =="
sed -n '1,80p' internal/runtime/executor/subworkflow.go

Repository: dagucloud/dagu

Length of output: 50371


🏁 Script executed:

#!/bin/sh
set -eu

echo "== loadInProcessDAG and newAgent call sites =="
rg -n -C5 'dag, cleanup, err := loadInProcessDAG\(|loadInProcessDAG\(' internal/subflow/local.go internal/subflow/local_test.go

echo
echo "== newAgent callers =="
rg -n -C2 'newAgent\(' internal/subflow/local.go

echo
echo "== PrepareDAG WorkDir handling source =="
sed -n '1,130p' internal/tools/prepare.go

echo
echo "== runParams.WorkDir setters/usages =="
rg -n -C3 'type RunParams|RunParams|WorkDir\(|SetWorkingDir|WorkingDir' internal/runtime/executor internal/runtime/dag --glob '*.go'

echo
echo "== local subflow tests around workdir/workspace =="
rg -n -C4 'WorkDir|workspace|Workspace|with:' internal/subflow/internal/subflow/local_test.go internal/subflow/local_test.go

echo
echo "== files named internal/subflow/internal/subflow/local_test.go if exists and current names =="
find internal/subflow -maxdepth 2 -type f \( -name '*local*test*.go' -o -name 'local_test.go' \) -print

Repository: dagucloud/dagu

Length of output: 11703


🏁 Script executed:

#!/bin/sh
set -eu

echo "== ReadStepMessages callers for WorkDir in runtime =="
rg -n -C3 'ReadStepMessages\(|WriteStepMessages\(|WorkDir\(' internal/runtime/agent internal/subflow internal/runtime/runstate --glob '*.go'

echo
echo "== SubDAGExecutor RunParams setters/callers =="
rg -n -C4 'Execute\(ctx .*RunParams|Reuse\(ctx .*RunParams|Retry\(ctx .*RunParams|\.Execute\(|\.Reuse\(|\.Retry\(' internal/runtime/executor internal --glob '*.go' | rg -C4 'SubDAGExecutor|Execute\(|Reuse\(|Retry\(|RunParams|workDir'

echo
echo "== DAG WorkingDir and WorkingDir methods =="
rg -n -C3 'WorkingDir|string WorkingDir|DefaultWorkingDir|WithDefaultWorkingDir|setWorkingDir|workingDir' internal/core internal --glob '*.go' | head -n 220

Repository: dagucloud/dagu

Length of output: 50371


Use the request-level working directory for tool preparation.

loadInProcessDAG() builds the child DAG from req.WorkDir, and SubDAGExecutor.Execute/Rewire pass the caller-supplied workDir into the request, but prepareDAGTools() still uses dag.WorkingDir. Pass the resolved request work directory instead so tools are installed relative to the same directory the child agent will execute in.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/subflow/local.go` around lines 406 - 416, The prepareDAGTools method
currently derives WorkDir from dag.WorkingDir instead of the request-level
directory used by loadInProcessDAG and SubDAGExecutor.Execute/Rewire. Update its
work-directory resolution to use the resolved request workDir passed through
rCtx, and provide that value to dagutools.InstallOptions so tool installation
matches the child agent’s execution directory.

Copilot AI review requested due to automatic review settings July 27, 2026 09:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@yohamta0
yohamta0 merged commit fb2f7b0 into main Jul 27, 2026
11 checks passed
@yohamta0
yohamta0 deleted the agent/fix-subdag-tools branch July 27, 2026 10:19
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.

tools declared in a child DAG are not installed when it runs as a sub-DAG, breaking official actions

2 participants