dagger/dagger · error
path modified in one changeset and removed in the other
Error message
path modified in one changeset and removed in the other
What it means
ErrModifiedRemoved is a sentinel error returned when one changeset modifies a path while the other removes it. This modify/delete conflict is unreconcilable automatically, so the merge fails with a conflict entry; the path is also called out specially in the aggregate message (Conflicts.Error groups these paths).
Source
Thrown at core/changeset.go:898
type ChangeType int
const (
ChangeTypeAdded ChangeType = iota
ChangeTypeModified
ChangeTypeRemoved
)
type Conflict struct {
Path string
Self ChangeType
Other ChangeType
Err error
}
var (
ErrAddedTwice = errors.New("path added in both changesets")
ErrModifiedTwice = errors.New("path modified in both changesets")
ErrModifiedRemoved = errors.New("path modified in one changeset and removed in the other")
)
type Conflicts []Conflict
func (conflicts Conflicts) Error() (err error) {
for _, c := range conflicts {
err = errors.Join(err, fmt.Errorf("conflict between changesets at path %q: %w", c.Path, c.Err))
}
return err
}
func (conflicts Conflicts) IsEmpty() bool {
return len(conflicts) == 0
}
func (conflicts Conflicts) ModifyDeletePaths() []string {
var paths []string
for _, c := range conflicts {View on GitHub (pinned to 82ba2681db)
Solutions
- Decide whether the file should exist: drop the modification from the surviving changeset, or re-add the file in the deleting one.
- Rebase one changeset onto the other so the delete supersedes (or incorporates) the modification.
- Use errors.Is(err, ErrModifiedRemoved) to find affected paths via the Conflicts list and resolve each explicitly.
- Rebuild a single changeset reflecting the intended final state of the path.
Example fix
// before
merged, err := cs1.Merge(cs2) // fails: modified in one, removed in other
// after
if conflicts, ok := err.(changeset.Conflicts); ok {
for _, c := range conflicts {
if errors.Is(c.Err, changeset.ErrModifiedRemoved) {
// choose: keep the delete
cs1 = cs1.WithoutPath(c.Path)
}
}
merged, err = cs1.Merge(cs2)
} Defensive patterns
Strategy: type-guard
Validate before calling
// detect modify/delete pairs before merge
removed, modified := map[string]bool{}, map[string]bool{}
for _, c := range cs1.Changes() { (map[core.ChangeType]func(){...}) } // collect Removed into removed, Modified into modified
// conflict if removed[p] && modified[p] for any p Type guard
func isModifiedRemoved(err error) bool { return errors.Is(err, core.ErrModifiedRemoved) } Try / catch
if err := cs1.Merge(cs2); err != nil {
if errors.Is(err, core.ErrModifiedRemoved) {
// decide keep-vs-delete for the reported paths and rebuild one changeset
}
return err
} Prevention
- Decide file lifecycle (keep or delete) before parallel edits.
- Rebase deletions against later modifications and vice versa.
- List Removed paths in one changeset and Modified in the other before merging.
When it happens
Trigger: Merging changesets where one has ChangeTypeModified and the other ChangeTypeRemoved for the same path (core/changeset.go:965); detected in Conflicts.Error's special-case formatting at core/changeset.go:917.
Common situations: One session edited a file while another (or the base snapshot) deleted it; stale changesets replayed after files were removed; parallel refactors where one branch deletes a file another updates.
Related errors
- path added in both changesets
- path modified in both changesets
- conflict between changesets at path %q: %w
- get changeset diff directory: %w
- evaluate changeset diff directory: %w
AI-assisted analysis of dagger/dagger@82ba2681db (2026-09-05).
Data as JSON: /api/errors/500668849819a35d.
Report an issue: GitHub.