Skip to content

core: add run-count history retention#2053

Merged
yottahmd merged 3 commits intodagucloud:mainfrom
reachyogeshp-afk:hist-retention-runs
Apr 30, 2026
Merged

core: add run-count history retention#2053
yottahmd merged 3 commits intodagucloud:mainfrom
reachyogeshp-afk:hist-retention-runs

Conversation

@reachyogeshp-afk
Copy link
Copy Markdown
Contributor

@reachyogeshp-afk reachyogeshp-afk commented Apr 29, 2026

Summary

  • Add hist_retention_runs DAG config to retain history by most recent run count.
  • Make hist_retention_days and hist_retention_runs mutually exclusive.
  • Preserve active and status-initializing runs during run-count cleanup.
  • Expose the field through API/schema/frontend types and docs/examples.

Fixes #1069

Testing

  • make lint
  • make api-validate
  • pnpm --dir ui gen:api
  • make api
  • go test ./internal/core ./internal/core/spec ./internal/core/exec ./internal/persis/filedagrun -count=1
  • go test ./internal/runtime/agent -run ^TestAgent_Run$/^DeleteOldHistoryByRuns -count=1
  • go test ./api/v1 ./internal/service/frontend/api/v1 -run ^TestToDAGDetailsIncludesHistoryRetentionRuns -count=1
  • go test ./internal/core/spec ./internal/persis/filedagrun -count=1

Manual feature test:

make bin

export DAGU_HOME=/tmp/dagu-retention-test
rm -rf "$DAGU_HOME"
mkdir -p "$DAGU_HOME/dags"

Created $DAGU_HOME/dags/retention-runs.yaml:

hist_retention_runs: 3

steps:
  - name: hello
    command: echo "hello"

Ran five DAG runs:

DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu start retention-runs --run-id run-1
DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu start retention-runs --run-id run-2
DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu start retention-runs --run-id run-3
DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu start retention-runs --run-id run-4
DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu start retention-runs --run-id run-5

Verified only the latest three runs remain:

yk@yk-air dagu % DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu history retention-runs --last 365d
DAG NAME        RUN ID  STATUS     STARTED (UTC)        DURATION  PARAMS
retention-runs  run-5   Succeeded  2026-04-30 03:47:49  < 1s      -
retention-runs  run-4   Succeeded  2026-04-30 03:47:47  < 1s      -
retention-runs  run-3   Succeeded  2026-04-30 03:47:44  < 1s      -

Verified mutually exclusive validation by configuring both fields:

hist_retention_days: 30
hist_retention_runs: 3

steps:
  - name: hello
    command: echo "hello"
yk@yk-air dagu % DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu validate retention-runs
Error: Validation failed for retention-runs
- field 'hist_retention_runs': hist_retention_days and hist_retention_runs cannot both be specified (value: 3)

Verified explicit hist_retention_days: 0 can override inherited run-count retention:

cat > "$DAGU_HOME/base.yaml" <<'YAML'
hist_retention_runs: 5
YAML

cat > "$DAGU_HOME/dags/retention-runs.yaml" <<'YAML'
hist_retention_days: 0

steps:
  - name: hello
    command: echo "hello"
YAML

DAGU_HOME=/tmp/dagu-retention-test DAGU_AUTH_MODE=none .local/bin/dagu validate retention-runs

Expected: validation succeeds.

Note: full make test was run but failed under full-suite load in integration/flaky areas; each failing test passed when rerun individually.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

This PR introduces run-count-based DAG history retention as an alternative to day-based retention. A new histRetentionRuns field is added across the core DAG model, API schemas, and persistence layers, with validation ensuring mutual exclusivity between run and day retention modes.

Changes

Cohort / File(s) Summary
API Schema & Code Generation
api/v1/api.gen.go, api/v1/api.yaml, ui/src/api/v1/schema.ts
Added histRetentionRuns field to DAGDetails schema. Regenerated OpenAPI spec and TypeScript type definitions.
Core DAG Model
internal/core/dag.go, internal/core/dag_test.go
Added HistRetentionRuns field to DAG struct. Updated default initialization logic to apply default HistRetentionDays = 30 only when neither retention setting is provided. Added test coverage for new defaulting behavior.
DAG Spec Build & Validation
internal/core/spec/dag.go, internal/core/spec/dag_test.go, internal/core/spec/key_hints.go
Added buildHistRetentionRuns transformer with validation (>0). Implemented mutual exclusivity check rejecting both retention modes simultaneously. Extended key hint mapping for snake_case conversion. Added unit test for build function.
Spec Loading & Merging
internal/core/spec/loader.go, internal/core/spec/loader_test.go
Integrated history retention override logic in processDAGDocument to enforce mutual exclusivity post-merge. Added test coverage for defaulting, single-mode configuration, error cases, and base/child override precedence.
DAG Run Cleanup by Count
internal/core/exec/dagrun.go, internal/persis/filedagrun/dataroot.go, internal/persis/filedagrun/dataroot_test.go, internal/persis/filedagrun/store.go
Added WithRetentionRuns option and RetentionRuns field to removal options. Implemented RemoveOldByRuns method with deterministic sorting (timestamp + baseDir). Updated RemoveOldDAGRuns to delegate to run-count cleanup when configured. Added comprehensive test cases.
Runtime Execution Integration
internal/runtime/agent/agent.go, internal/runtime/agent/agent_test.go
Refactored setupDAGRunAttempt to conditionally apply run-count or day-count cleanup based on configuration. Unified attempt variable handling. Added test verifying run-count retention enforcement.
API Transformation
internal/service/frontend/api/v1/transformer.go, internal/service/frontend/api/v1/transformer_test.go
Updated toDAGDetails to populate HistRetentionRuns field from core DAG. Added test coverage.
Configuration & Documentation
internal/cmn/schema/dag.schema.json, internal/persis/filebaseconfig/default_base_config.go, internal/persis/filedag/examples_unix.go, internal/persis/filedag/examples_windows.go, skills/dagu/references/schema.md
Added hist_retention_runs to JSON schema with validation. Updated default base config and examples with mutual exclusivity clarification. Updated documentation.

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Agent<br/>(setupDAGRunAttempt)
    participant Store as DAGRun Store
    participant DataRoot as DataRoot
    participant Persist as File System

    Agent->>+Store: RemoveOldDAGRuns(ctx, name, opts...)
    alt RetentionRuns configured
        Store->>Store: Parse options.RetentionRuns
        Store->>+DataRoot: RemoveOldByRuns(retentionRuns, dryRun)
        DataRoot->>DataRoot: List all runs, sort by timestamp+baseDir
        DataRoot->>DataRoot: Keep newest N runs
        DataRoot->>DataRoot: Filter active runs (cannot remove)
        loop For each removable run
            DataRoot->>Persist: Delete run directory & metadata
        end
        DataRoot->>-Store: Return removed run IDs
    else RetentionDays configured
        Store->>+DataRoot: RemoveOld(retentionDays, dryRun)
        DataRoot->>DataRoot: Filter runs by mod-time cutoff
        DataRoot->>Persist: Delete old run records
        DataRoot->>-Store: Return removed run IDs
    end
    Store->>-Agent: Cleanup complete
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% 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
Title check ✅ Passed The title 'core: add run-count history retention' directly and specifically summarizes the main change: introducing run-count-based history retention for DAGs.
Linked Issues check ✅ Passed All coding objectives from issue #1069 are met: run-count retention field added, mutually exclusive with days retention, active runs preserved, and exposed through API/schema/frontend.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing run-count history retention feature; no unrelated modifications detected across core, spec, persistence, runtime, API, and documentation files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@reachyogeshp-afk reachyogeshp-afk marked this pull request as ready for review April 30, 2026 04:01
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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
api/v1/api.yaml (1)

8608-8610: ⚡ Quick win

Clarify mutual exclusivity in the field description.

Please explicitly document that histRetentionRuns is mutually exclusive with histRetentionDays in this schema description so generated clients/UI validations don’t misinterpret allowed combinations.

♻️ Proposed OpenAPI description update
         histRetentionRuns:
           type: integer
-          description: "Number of DAG runs to retain historical logs"
+          description: "Number of DAG runs to retain historical logs. Mutually exclusive with `histRetentionDays`."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v1/api.yaml` around lines 8608 - 8610, Update the OpenAPI description for
the histRetentionRuns field to state it is mutually exclusive with
histRetentionDays: modify the description of histRetentionRuns to explicitly
mention "Mutually exclusive with histRetentionDays" and, if applicable, add a
similar note to the histRetentionDays description; ensure the text references
both field names (histRetentionRuns, histRetentionDays) so generated clients and
UI validators can detect the exclusivity constraint.
internal/core/spec/dag_test.go (1)

661-670: ⚡ Quick win

Add a negative-value case to complete boundary validation.

The builder rejects <= 0, but the table only checks 0. Adding a negative case improves regression coverage for the same rule.

Proposed test addition
 	tests := []struct {
 		name        string
 		input       *int
 		expected    int
 		errContains string
 	}{
 		{name: "NilDefaultsTo0", input: nil, expected: 0},
 		{name: "CustomValue", input: &runs, expected: 3},
 		{name: "ZeroValueInvalid", input: &zero, errContains: "hist_retention_runs must be > 0"},
+		{name: "NegativeValueInvalid", input: new(-1), errContains: "hist_retention_runs must be > 0"},
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/spec/dag_test.go` around lines 661 - 670, The test table in
dag_test.go only checks zero as an invalid value but the builder rejects any
value <= 0; add a negative-value case to the tests slice (e.g., add a test named
"NegativeValueInvalid" with input pointing to a negative int variable like neg
and errContains "hist_retention_runs must be > 0") and ensure you declare the
neg variable (e.g., neg := -1) alongside runs/zero so the test verifies the <= 0
boundary is enforced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/spec/dag.go`:
- Around line 682-688: applyHistoryRetentionOverride currently treats zero as
"unset" because it checks built ints (current.HistRetentionDays > 0), so an
explicit hist_retention_days: 0 in the raw spec cannot override an inherited
HistRetentionRuns; change the logic to inspect the authored/raw pointer field on
the incoming DAG (e.g., check current.Raw.HistRetentionDays != nil and
current.Raw.HistRetentionRuns != nil or whatever *int raw fields exist) and use
those presence checks to clear the opposite mode on effective (still mutate
effective.HistRetentionDays and effective.HistRetentionRuns as now) so explicit
zeroes in current override inherited settings.

In `@internal/persis/filedagrun/dataroot.go`:
- Around line 411-430: LatestAttempt only returns attempts with a status file so
fresh attempts (created by CreateAttempt in internal/runtime/agent/agent.go) can
be invisible and cause an older finished attempt to mark the DAGRun removable;
change the removal logic in this block to first detect any newer status-less
attempts and bail if any exist: call the repository method that lists attempts
(or add a helper like HasAttemptWithoutStatus or ListAttempts) for the same run
ID and check for any attempt whose creation time is after the returned
latestAttempt or that has no status file, and if found return false,nil (or
alternatively explicitly exclude the current dagRunID from run-count cleanup
until its initial status is persisted). Ensure you reference LatestAttempt,
ModTime, ReadStatus, CreateAttempt and the run-count cleanup path when adding
the new check.

---

Nitpick comments:
In `@api/v1/api.yaml`:
- Around line 8608-8610: Update the OpenAPI description for the
histRetentionRuns field to state it is mutually exclusive with
histRetentionDays: modify the description of histRetentionRuns to explicitly
mention "Mutually exclusive with histRetentionDays" and, if applicable, add a
similar note to the histRetentionDays description; ensure the text references
both field names (histRetentionRuns, histRetentionDays) so generated clients and
UI validators can detect the exclusivity constraint.

In `@internal/core/spec/dag_test.go`:
- Around line 661-670: The test table in dag_test.go only checks zero as an
invalid value but the builder rejects any value <= 0; add a negative-value case
to the tests slice (e.g., add a test named "NegativeValueInvalid" with input
pointing to a negative int variable like neg and errContains
"hist_retention_runs must be > 0") and ensure you declare the neg variable
(e.g., neg := -1) alongside runs/zero so the test verifies the <= 0 boundary is
enforced.
🪄 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

Run ID: 8074b540-4311-4ad9-9f37-3c67be6a7844

📥 Commits

Reviewing files that changed from the base of the PR and between 405cc68 and 5a57dff.

📒 Files selected for processing (23)
  • api/v1/api.gen.go
  • api/v1/api.yaml
  • internal/cmn/schema/dag.schema.json
  • internal/core/dag.go
  • internal/core/dag_test.go
  • internal/core/exec/dagrun.go
  • internal/core/spec/dag.go
  • internal/core/spec/dag_test.go
  • internal/core/spec/key_hints.go
  • internal/core/spec/loader.go
  • internal/core/spec/loader_test.go
  • internal/persis/filebaseconfig/default_base_config.go
  • internal/persis/filedag/examples_unix.go
  • internal/persis/filedag/examples_windows.go
  • internal/persis/filedagrun/dataroot.go
  • internal/persis/filedagrun/dataroot_test.go
  • internal/persis/filedagrun/store.go
  • internal/runtime/agent/agent.go
  • internal/runtime/agent/agent_test.go
  • internal/service/frontend/api/v1/transformer.go
  • internal/service/frontend/api/v1/transformer_test.go
  • skills/dagu/references/schema.md
  • ui/src/api/v1/schema.ts

Comment thread internal/core/spec/dag.go Outdated
Comment thread internal/persis/filedagrun/dataroot.go
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd left a comment

Choose a reason for hiding this comment

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

LGTM, thank you very much for implementing this!

@reachyogeshp-afk
Copy link
Copy Markdown
Contributor Author

LGTM, thank you very much for implementing this!

Thanks for quick review. I have rebased with main . LMK if anything else is required at my end.

@yottahmd yottahmd merged commit 1677bcc into dagucloud:main Apr 30, 2026
11 checks passed
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.

Limit dag run history by number of runs

2 participants