# Crash: null ProjectItem deref in `EditSnapshot::refresh()` — every `EditFileOperations` construction, for hosts not using `ProjectManager` projects
**Engine:** `develop` @ `494e91d2ff5` (3.5.0). Still present at the time of writing.
**Platform:** macOS 13.7.8, AppleClang 14.0.3 — but this is not platform-specific.
## Summary
`EditSnapshot::refresh()` guards the `ProjectItem` on one line and then dereferences it
unguarded on the next:
```cpp
void EditSnapshot::refresh()
{
clear();
auto pi = engine.getProjectManager().getProjectItem (sourceFile);
...
name = pi ? pi->getName() : sourceFile.getFileNameWithoutExtension(); // guarded
setState (newState, TimeDuration::fromSeconds (pi->getLength())); // not guarded
refreshFromState();
}
```
`modules/tracktion_engine/model/edit/tracktion_EditSnapshot.cpp:227`
If the Edit has a file but no `ProjectItem`, `pi` is null and this crashes.
## Why it matters more than it looks
The deref itself is long-standing, but it used to be unreachable. `getEditSnapshot()` was
keyed by `ProjectItemID` and bailed out early on an invalid one:
```cpp
if (itemID.isInvalid())
return {};
```
Since `f0964b0a7da` (“EditClip: Fixed up more path related issues…”, 2026-03-20) snapshots
are keyed by file, and the early-out is now:
```cpp
if (editFile == juce::File())
return {};
```
That only catches an *absent* file. An Edit that has a real file but no `ProjectItem` now
sails past the guard, constructs an `EditSnapshot`, and the constructor calls `refresh()`
immediately.
## Call chain
Nothing exotic is required to reach it — merely constructing `EditFileOperations` does:
```
EditFileOperations::EditFileOperations (Edit&)
└─ SharedDataPimpl (e)
└─ SharedEditFileDataCache::get (edit)
└─ std::make_shared<Data> (edit)
└─ member init:
EditSnapshot::getEditSnapshot (edit.engine, edit.editFileRetriever())
└─ new EditSnapshot (engine, file)
└─ refresh() ← null deref
```
So any host that sets `Edit::Options::editFileRetriever` to a real file, but manages its
own storage rather than going through `ProjectManager`, crashes on **every save** — that
is where `EditFileOperations` is normally constructed.
## Reproduce
1. Create or load an `Edit` with an `editFileRetriever` returning a real file on disk.
2. Do not register that file with `ProjectManager` (no `ProjectItem` for it).
3. Construct `EditFileOperations (edit)` — e.g. call `writeToFile()`.
Crashes in `EditSnapshot::refresh()`. In a debug build it surfaces first as the
`jassert (referencedObject != nullptr)` in `ReferenceCountedObjectPtr::operator->`
(`juce_ReferenceCountedObject.h:406`).
## Suggested fix
Guard the length exactly the way the name above it is already guarded:
```cpp
setState (newState, TimeDuration::fromSeconds (pi != nullptr ? pi->getLength() : 0.0));
```
We have been running this locally with no ill effects. `refreshFromState()` follows
immediately, and for a host in this situation nothing consumes the snapshot length.
It may also be worth reconsidering whether `getEditSnapshot()` should return `{}` when
there is no `ProjectItem` for the file, which would restore the previous behaviour more
faithfully than patching the deref — but the one-line guard is the minimal fix.
## Context
Found while moving a host app from a 3.1-era pin to current `develop`. The app stores
Edits in its own bundle format rather than as `ProjectManager` projects, which is why it
hits this on the first save after the bump.