Skip to content

fix(maintenance): restart for stores deleted under the daemon (#4099) - #4130

Merged
ScriptedAlchemy merged 3 commits into
masterfrom
fix/4099-store-identity
Oct 16, 2026
Merged

ScriptedAlchemy merged 3 commits into
masterfrom
fix/4099-store-identity

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Part of #4099.

In #4099 a profile was deleted under a running daemon. The daemon kept writing into the unlinked database files and filled 112 GB that no size census could see. Deleting files does not free that space while the daemon holds them open, and nothing checked a store's path after attach.

Every full maintenance tick now stats each mounted store path and compares its device and inode with the file recorded at mount. A store whose path is gone or names a different file enters the reset-required state for the life of the daemon, with tracedecay daemon restart as its remedy. That surfaces in three places:

  • tracedecay doctor lists it as a pending operator action naming the restart.
  • tracedecay daemon status and tracedecay_status count it among reset-required stores.
  • tracedecay wipe --stale refuses it and names the restart, because no wipe frees an unlinked inode.

The daemon logs store_file_identity_lost once per store.

The storage redesign (#4118, section 5) closes the lane and reopens it on the next job. That needs the shared executor. Until then a restart is the only way to release the descriptors.

Verification

  • daemon::maintenance::tests::a_mounted_store_deleted_under_the_daemon_requires_a_restart mounts a real store's telemetry port, deletes the file, runs the tick's identity check, and asserts the store is reset-required with the restart remedy. Mutation check: with the check dropped from the tick helper, the test fails.
  • telemetry::store_file_identity_tests covers current, replaced (rename over the path), and missing files, and checks that a rebind through the held handle does not adopt a replacement.
  • session_registry::maintenance::tests::a_store_file_lost_while_open_requires_a_daemon_restart covers one entry per path, the restart remedy, and that wipe --stale refuses the label.
  • Unit suites for tracedecay-maintenance, tracedecay-store-runtime, and tracedecay. Clippy and rustfmt_check_2024 for every touched crate.

A daemon held store files open after the profile was deleted and wrote 112 GB into unlinked inodes (#4099). Each full maintenance tick now compares every mounted store path with the file it mounted; a missing or replaced file enters the reset-required state naming `tracedecay daemon restart`, which doctor, daemon status and wipe --stale report.
@changeset-bot

changeset-bot Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 31b986d

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ScriptedAlchemy ScriptedAlchemy changed the title fix(maintenance): require a restart for stores deleted under the daemon (#4099) fix(maintenance): restart for stores deleted under the daemon (#4099) Oct 16, 2026

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 2 potential issues.

2 flags not posted on this PR by your GitHub settings — view them in Devin Review. (Configure)

Devin Review

Comment on lines +875 to +879
if continuation.is_none()
&& let Some(registry) = administration.mounted_session_runtime_registry().await
{
record_lost_store_files(&maintenance_observations, &registry);
}

@devin-ai-integration devin-ai-integration Bot Oct 16, 2026 •

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.

🔴 Disabled retention hides deleted stores

When retention is disabled, record_lost_store_files never runs. MaintenanceCoordinator::spawn skips the entire retention loop, leaving deleted mounted stores absent from restart warnings.

Learn more

The new identity check runs only inside the retention tick. MaintenanceCoordinator::spawn returns without starting that tick when all retention policies are disabled. A disabled retention policy does not close mounted store handles. The daemon can therefore keep those handles without ever publishing a restart-required state.

Example: An operator disables session, observation, orphan, and compaction retention. After a project store is mounted and its database file is deleted, the daemon never runs an identity check or lists the restart action.

Recommended fix: Give store-identity checks an independent daemon-owned cadence that starts regardless of retention policy, and exercise the disabled-retention configuration in a production-path test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +509 to +524
pub fn check_store_identities(&self) -> Vec<(PathBuf, StoreFileIdentityStateV1)> {
let mounted: Vec<_> = self
.ports
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.iter()
.map(|(path, cached)| (path.clone(), cached.file_identity))
.collect();
mounted
.into_iter()
.map(|(path, identity)| {
let state = store_file_identity_state(&path, identity);
(path, state)
})
.collect()
}

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.

🔴 Projectless profile stores escape deletion checks

When no project route has been composed, check_store_identities has no entry for mounted profile stores. register_route_store_telemetry alone registers their ports, so projectless profile activity receives no restart warning after deletion.

Learn more

The identity census enumerates only the telemetry port map. register_route_store_telemetry is the sole production registrar and runs during project composition, not when the profile authority is mounted. attach_reset_required_stores can mount the profile registry for projectless initialize requests without composing a project. Such a mounted profile database has no identity record to compare, even though its handle remains open.

Example: A daemon serves only projectless initialize/status requests and mounts its profile authority. The profile database file is removed before any project is opened. The census stays empty and no restart action appears.

Recommended fix: Record identity as part of each canonical store mount, including profile and other non-project store mounts, instead of relying on route telemetry registration.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@ScriptedAlchemy
ScriptedAlchemy merged commit 12fba92 into master Oct 16, 2026
5 checks passed
neoneye pushed a commit to agent-memory-atlas-archive/ScriptedAlchemy--tracedecay that referenced this pull request Oct 28, 2026
The writer's probe now releases a lost store's files, so the restart
ScriptedAlchemy#4130 names only remounts the owners that keep the closed runtime.
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.

1 participant