Skip to content

Release v1.1.0-rc.7: backfill, scheduler hardening, and Docker sync#38

Merged
LAP87 merged 12 commits into
mainfrom
codex/operator-ui-refresh
Jul 25, 2026
Merged

Release v1.1.0-rc.7: backfill, scheduler hardening, and Docker sync#38
LAP87 merged 12 commits into
mainfrom
codex/operator-ui-refresh

Conversation

@LAP87

@LAP87 LAP87 commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • adds monitored-empty Radarr/Sonarr backfill with dry-run, direct linking, bounded acquisition, and refresh
  • hardens scheduler deduplication, recurrence handling, safety backups, snapshot import, and daemon heartbeats
  • makes symlink create/replace operations atomic and race-safe; fixes importer, repair, and multi-version edge cases
  • refreshes operator UI, API/CLI docs, dependencies, CI, release tags, and Docker daemon defaults

Verification

  • cargo fmt --all -- --check
  • cargo test --all-targets --locked: 854 passed, 1 ignored
  • cargo clippy --all-targets --all-features --locked -- -D warnings
  • cargo audit: 0 vulnerabilities
  • npm audit: 0 vulnerabilities
  • cargo build --release --locked
  • local Docker image built and validated against production config
  • isolated daemon smoke: healthy, scheduler and web started, auth enforced

Release

Version: 1.1.0-rc.7. Prerelease images publish to the rc tag; stable releases publish to latest. No Rust toolchain pin was added.

@LAP87 LAP87 changed the title Add operator UI refresh slice Prepare v1.1.0-rc.1 Apr 29, 2026
@LAP87
LAP87 changed the base branch from codex/add-webui-icon to main April 29, 2026 21:07
@LAP87

LAP87 commented Apr 29, 2026

Copy link
Copy Markdown
Owner Author

@codex please review this v1.1.0-rc.1 release branch. Focus on provider import, force/safe web flow, multi-version linking, deferred media-server refresh, Docker/runtime defaults, and docs readability.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f60fc8fe6c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/importer.rs Outdated
Comment on lines +595 to +599
ImportContentType::Movie | ImportContentType::Auto => {
let Some(tmdb) = tmdb else {
return Ok(RemoteLookupOutcome::NoMatch);
};
let matches = tmdb.search_movie(query, candidate.year_hint).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle auto import lookups with TV-aware providers

When content_type is auto, remote resolution currently goes straight to TMDB movie search, so TV folders/files without explicit IDs are either left unresolved or assigned a movie ID. In mixed provider roots (--content-type auto --lookup-mode remote), this can produce wrong {tmdb-...} tags and downstream placement decisions for TV content because no TVDB/TMDB-TV path is attempted in this branch.

Useful? React with 👍 / 👎.

Comment thread src/commands/importer.rs
Comment on lines +1429 to +1432
if id.starts_with("tmdb-") || id.starts_with("imdb-") {
return rules_plan
.and_then(|rules| rules.rules.destinations.movies.destination_for(candidate))
.or_else(|| options.movie_destination.clone());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route tmdb-tagged auto imports by actual media type

Auto-destination routing treats every tmdb-* ID as a movie, but TMDB IDs are used for both movies and TV entries. A TV source already tagged like {tmdb-12345} will be forced into the movie destination path, which can misplace imports and poison later link/backfill state for mixed libraries.

Useful? React with 👍 / 👎.

Comment thread src/linker.rs
Comment on lines +722 to +726
let filename = if self.multi_version {
append_version_label(
&filename,
&m.source_item.extension,
&version_label(&m.source_item),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict multi-version suffixing to movie targets

The multi_version toggle is applied to episode filename generation as well, even though the shipped config/docs describe this as movie-only behavior. Enabling it will append per-version suffixes to TV/anime episode links too, changing established episode naming and creating extra per-episode variants that operators likely did not opt into.

Useful? React with 👍 / 👎.

@LAP87

LAP87 commented Apr 30, 2026

Copy link
Copy Markdown
Owner Author

Addressed the Codex review findings in a95ee7c:

  • Auto import now carries resolved content type through cache/remote lookup so TMDB-TV results can route/backfill as TV instead of movie.
  • Auto remote lookup now uses TVDB/TMDB-TV for TV/anime-looking candidates instead of always starting with TMDB movie search.
  • TMDB-tagged TV-looking auto imports route to TV destinations.
  • Multi-version suffixing is limited to movie targets for now, matching the movie-first docs.

Validation:

  • cargo fmt --all -- --check
  • cargo test --all-targets --locked: 798 passed; 0 failed; 1 ignored
  • cargo clippy --all-targets --all-features --locked -- -D warnings
  • cargo build --release --locked
  • local Docker symlinkarr:1.1.0-rc.2 version smoke passed

@LAP87 LAP87 changed the title Prepare v1.1.0-rc.1 Prepare v1.1.0-rc.2 Apr 30, 2026
@LAP87

LAP87 commented Apr 30, 2026

Copy link
Copy Markdown
Owner Author

@codex review the latest rc.2 changes in a95ee7c, especially the previous import routing and movie-only multi-version findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a95ee7c551

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/importer.rs
Comment on lines +1849 to +1851
|| (id.starts_with("tmdb-")
&& infer_auto_content_type(candidate) == ImportContentReport::Tv)
{

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle inferred-anime TMDB imports as TV records

destination_for_auto_candidate now routes inferred anime paths to the anime destination, but media_type_for_import_candidate only reclassifies tmdb-* IDs when inference is Tv. For --content-type auto paths like /.../anime/... {tmdb-123}, the import can be written under anime while backfill still stores MediaType::Movie, which sends later repair/scan logic down movie-specific paths for episodic content. This mismatch is introduced by the new auto-routing behavior and should also treat inferred Anime as TV-family media when deriving LinkRecord.media_type.

Useful? React with 👍 / 👎.

Comment thread src/commands/importer.rs
Comment on lines +491 to +493
ImportContentType::Movie => tmdb.is_some(),
ImportContentType::Auto => tmdb.is_some() || tvdb.is_some(),
ImportContentType::Tv | ImportContentType::Anime => tmdb.is_some() || tvdb.is_some(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid spending lookup budget on unqueryable auto candidates

In resolve_candidates_from_remote, auto mode is now considered usable with only TVDB credentials, but the lookup loop still increments the max_lookups counter before checking whether a candidate can actually be queried with available providers. When TMDB is missing, movie-inferred auto candidates immediately return NoMatch yet still consume the cap, so later TV candidates may never be queried against TVDB in the same run.

Useful? React with 👍 / 👎.

@LAP87
LAP87 force-pushed the codex/operator-ui-refresh branch from 846d516 to b2e45d4 Compare May 19, 2026 21:35
@LAP87 LAP87 changed the title Prepare v1.1.0-rc.2 Release v1.1.0-rc.7: backfill, scheduler hardening, and Docker sync Jul 25, 2026
@LAP87
LAP87 marked this pull request as ready for review July 25, 2026 23:29
@LAP87
LAP87 merged commit a918a3c into main Jul 25, 2026
9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 62745a074c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/matcher.rs
Comment on lines +849 to +853
let key = VersionedDestinationKey {
destination,
version_slot: version_slot(&candidate.source_item),
};
insert_or_replace(&mut by_destination, key, candidate);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve each source in multi-version reduction

When symlink.multi_version is enabled, two movie sources with the same parsed quality/edition/HDR/codec—or any two untagged sources, which both use "version"—produce the same VersionedDestinationKey, so insert_or_replace silently discards one. The linker's path-hash suffix cannot distinguish these files because the discarded candidate never reaches it, preventing common same-resolution encodes from coexisting despite multi-version mode.

Useful? React with 👍 / 👎.

Comment thread src/web/api/scheduler.rs
Comment on lines +108 to +116
let permit = match state.scheduler_jobs().try_acquire_owned() {
Ok(permit) => permit,
Err(_) => {
return api_error(
StatusCode::CONFLICT,
"Another scheduler job is already running",
)
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Share scheduler exclusion with all background jobs

When the daemon and web UI run together, this semaphore serializes only API run-now requests: the daemon's run_scheduler_loop executes due rules without acquiring it, while ordinary web scans use a separate background_jobs guard. A scheduled scan can therefore overlap either a manual scan or a run-now scan, and the UI will still accept the second job; both then reconcile the same links and scan records concurrently, allowing one run's dead-link sweep or updates to interfere with the other.

Useful? React with 👍 / 👎.

Comment thread src/scheduler.rs
Comment on lines +330 to +337
if cfg.daemon.vacuum_enabled {
rules.push(ScheduleRule::new_bootstrap(
"Legacy VACUUM housekeeping",
ScheduledEvent::HousekeepingVacuum,
ScheduleTrigger::Daily {
times: vec![format!("{:02}:00", cfg.daemon.vacuum_hour_local)],
},
json!({ "vacuum": true }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Schedule retention housekeeping even when VACUUM is disabled

With the default daemon.vacuum_enabled: false, no housekeeping rule is created at all. The previous daemon loop always called housekeeping_with_vacuum(false) and used this flag only to decide whether to run the expensive VACUUM step, so after this scheduler migration default installations stop deleting expired API cache rows, old scan runs, link events, and completed acquisition jobs and let the database grow indefinitely.

Useful? React with 👍 / 👎.

Comment thread src/commands/backfill.rs
Comment on lines +147 to +149
fn has_media(&self, media_id: &str) -> bool {
self.targets_by_media_id.contains_key(media_id)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Scope active-link suppression to the candidate destination

When a media ID has any active DB link, this snapshot suppresses backfill without checking that the target belongs to the candidate's Arr path or selected library. For example, after moving an item to a new library root—or when another library already contains the same title—the old/other target makes has_media true, so an empty current Radarr destination is counted as already satisfied and --search-missing never submits it even though the folder being backfilled remains empty.

Useful? React with 👍 / 👎.

Comment thread src/web/handlers.rs
Comment on lines +1552 to +1554
let next_due = rule
.next_after(now)
.map(|dt| dt.format("%Y-%m-%d %H:%M:%S %Z").to_string());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Derive displayed due times from each rule's persisted cursor

For a startless interval rule, next_after(now) always returns now + interval, while the daemon schedules from the last persisted planned_at and treats a never-run rule as immediately due at created_at. Consequently the scheduler page's countdown resets whenever it is loaded—for example, a 15-minute rule that last ran 10 minutes ago is shown as due in 15 minutes although it will run in 5—and a newly created rule can execute immediately while the UI says its first run is one interval away.

Useful? React with 👍 / 👎.

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