langgenius / langgenius/dify

[Bug] Agent config skill/file replacement orphans ToolFile objects with no garbage collection

Open
#41,376 1 comment 1 reaction 0 assignees View on GitHub
1.17.0
Dominant language
TypeScript
Stars
156k
Forks
24.6k
Avg merge
20h 50m
Merged PRs (30d)
586

Description

### Self Checks

- [x] I have read the [Contributing Guide](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) and [Language Policy](https://github.com/langgenius/dify/issues/1542).
- [x] This is only for bug report, if you would like to ask a question, please head to [Discussions](https://github.com/langgenius/dify/discussions/categories/general).
- [x] I have searched for existing issues [search for existing issues](https://github.com/langgenius/dify/issues), including closed ones.
- [x] I confirm that I am using English to submit this report, otherwise it will be closed.
- [x] 【中文用户 & Non English User】请使用英语提交,否则会被关闭 :)
- [x] Please do not modify this template :) and fill in all the required fields.

### Dify version

1.17.0

### Cloud or Self Hosted

Self Hosted (Docker)

### Steps to reproduce

1. Self-hosted Dify 1.17.0, create an Agent app and open a Build/Debug session (so config mutations are allowed).
2. Inside the sandbox, create a skill directory and run `dify-agent config skills push `.
3. Modify a file in the skill, run the same `dify-agent config skills push ` again (same skill name).
4. Repeat a few times, then inspect the `tool_files` table (and the object storage / `storage` volume).

What happens per push (from source review of the `1.17.0` tag):

- the CLI always uploads a **fresh ToolFile** first (`dify-agent-runtime/internal/agentcli/config.go` → `uploadConfigFile` → `/agent-stub/files/upload-request`),
- `AgentConfigService._apply_skill_updates()` (`api/services/agent_config_service.py`) upserts the Soul entry **by name**, so the config view looks like a clean replace,
- the previous ToolFile row and its storage blob are never removed. The module docstring states this explicitly: *"It intentionally does not manage object-storage lifecycle: removing or replacing a config asset drops the Soul reference only."*

Note that retired agent workspaces / bindings / home snapshots **do** get collected asynchronously (`api/tasks/collect_agent_resources_task.py`), but there is no equivalent for unreferenced ToolFiles.

### ✔️ Expected Behavior

Replaced or deleted config skills/files should eventually have their backing ToolFile objects reclaimed (reference-counted against all Soul configs — snapshots + drafts of all kinds — and workspace-skill draft/published versions), ideally via a collection job similar to `collect_agent_resources_task.py` triggered after config push/apply transactions.

### ❌ Actual Behavior

Every push iteration silently leaves another orphaned ToolFile row + object-storage blob behind. Iterating on skills/files from build sessions (a natural tweak → push → test loop) accumulates storage without bound, with no cleanup path and no user-visible indication.

Contributor guide

Open the contributing guide

Research direction

Start with dify-agent-runtime/internal/agentcli/config.go and api/services/agent_config_service.py, tracing uploadConfigFile and _apply_skill_updates(). Compare the existing cleanup behavior in api/tasks/collect_agent_resources_task.py, then reproduce repeated skill pushes and inspect the tool_files table and storage volume. Done means replaced or deleted assets are eventually reclaimed without removing files still referenced by the listed Soul configurations or workspace-skill versions.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, python
Domain
backend, cloud, database
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.