Skip to content

fix(autoscan): poll at once after a marker reset, and not for a respelled URL - #2080

Open
blurbery wants to merge 2 commits into
Silo-Server:mainfrom
blurbery:fix/autoscan-reset-last-run
Open

blurbery wants to merge 2 commits into
Silo-Server:mainfrom
blurbery:fix/autoscan-reset-last-run

Conversation

@blurbery

@blurbery blurbery commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Related issue: #1230
Validation tasks: touches #1231 C3 and #1232 C1 (neither has a result yet)

When an admin edit resets an autoscan source's poll marker (#1935: a new connection or source_config, or a connection pointed at another server), the source's next poll starts from now, which is the documented behaviour. But last_run_at stays, so the scheduler still waits out the source's interval from its last run before that first poll. Anything the upstream imports between the edit and that poll is never reported. With the default 10-minute cycle and a source set to poll hourly, that's up to an hour of imports that never get a targeted scan. This change clears last_run_at along with the marker, so the source polls at the next cycle.

A second, smaller case: UpdateConnection treats a base URL that is only spelled differently (http://Sonarr:8989 saved as http://sonarr:8989/) as a new server and resets every bound source's marker, which skips history for no reason.

Approach

  • UpdateSource clears last_run_at in the same statement and under the same condition as the marker (connection or source_config changed). UpdateConnection clears it in the same statement as the bound sources' markers, including for a bound source that has run but has no marker yet (its first poll failed), so it retries the new URL at the next cycle. Other edits keep both.
  • The interval check in Service.poll already treats a source with no last_run_at as due, so the gap now ends at the next run of the poll task. If that first poll fails, RecordError stamps last_run_at again and the normal interval applies.
  • connectionUpstream.differsFrom compares base URLs with the scheme and host lower-cased, surrounding space trimmed and trailing slashes dropped. A different port, scheme or path still counts as a different server, and an escaped slash (%2F) still differs from a path separator. AdvanceMarker uses the same comparison, so a poll that overlaps such an edit still stores its marker.
  • TestUpdateSourceMarkerReset and TestUpdateConnectionMarkerReset are now database contracts, so Go DB pins runs them. Today no CI job sets a database for them.

What an admin sees: right after an edit that resets the marker, the source list shows "Not run yet" (or "Last poll failed" without a time, if the last poll failed) until the next cycle polls it, and the API returns last_run_at: null for that source in the meantime. Webhook sources aren't affected.

Validation

  • The two reset tests now also check last_run_at: cleared with the marker on a connection switch, an unbind, a source_config change and a connection URL change; kept on every edit that keeps the marker. Two new connection cases check that a trailing slash or a different host case keeps the markers, and every connection case includes a source whose first poll failed. Unit tests cover the URL comparison, including escaped paths. Against a local migrated PostgreSQL 18 database, all of these fail on ca186fe (last_run_at kept, markers reset for the respelled URLs) and pass on this branch.
  • go test ./internal/autoscan/ passes with and without a database. make lint-changed: clean.
  • CI on 7f7bfeb: every job passed, including Go DB pins, which now runs the two reset tests (run).
  • Not run against a live Sonarr or Radarr. The only autoscan source on my server is disabled, so I have no production run to show.

Benchmarks

Not applicable. One more column in two existing updates.

Evidence

Evidence: https://evidence.siloserver.org/r/silo-server/pr-2080/

The change a user can see is the status text and last_run_at above, between an edit and the next cycle. I don't have a capture from a running server; the evidence is the database test output.

Risks

  • A source whose marker was reset now polls at the next cycle even if its interval is longer. That's one extra poll, at most once per edit.
  • Two URLs that differ only by a default port (http://host and http://host:80) still count as different servers, as before.

Checklist

  • I read and can explain the complete diff.
  • This pull request addresses one concern.
  • The Evidence section shows every change a user can see, or says there is none.

AI Disclosure

  • Harness: Claude Code (desktop app)
  • Tool(s): Claude Code, go, gh
  • Model(s): claude-opus-5-5
  • Involvement: AI-assisted
  • Adversarial review: n/a, small change that extends fix(autoscan): reset poll markers when a source's upstream changes #1935's reset. Kody's review on this PR found that sources without a marker were skipped by the connection reset and that escaped paths compared equal; both are fixed and tested.

AI-assisted with Claude Opus. I directed the task and designed the work.

…lled URL

Clearing a source's marker on an edit made its next poll start from now,
but last_run_at stayed, so the source still waited out its interval
before that poll. Whatever the upstream imported in between was never
reported. Clear last_run_at with the marker in UpdateSource and
UpdateConnection, so the next cycle polls the source and the gap ends
at the next run of the poll task.

UpdateConnection also reset markers when the base URL was only spelled
differently (a trailing slash, a different host case), which skipped
history for no reason. Compare base URLs with the scheme and host
lower-cased and trailing slashes dropped.

Run the two reset tests as database contracts so CI covers them.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌

@coderabbitai

coderabbitai Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 16 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used all 4 included reviews currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: a4437f08-7be7-4334-83b4-729911404fdf
📥 Commits

Reviewing files that changed from the base of the PR and between ca186fe and 7f7bfeb.

📒 Files selected for processing (4)
  • internal/autoscan/repository.go
  • internal/autoscan/repository_source_marker_test.go
  • internal/autoscan/repository_upstream_test.go
  • scripts/ci/db-contracts.txt
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Quick104 Quick104 added priority: P2 Limited scope, workaround exists, or polish impact: polish Annoyance, cosmetic, or nice-to-have labels Oct 8, 2026 — with Cursor
@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

The PR explains why a running capture was unavailable, but the database test output does not show the user-facing changes. Please add this evidence if available:

  • Admin source list (internal/autoscan/repository.go, rendered in web/src/pages/admin/autoscan/sourceDisplay.ts): before-and-after screenshots of the same source row showing its status around a marker reset, at desktop and phone width, with the build or commit identified.
  • Earlier pickup of upstream-imported changes (internal/autoscan/repository.go enables the next poll): a short recording showing the reset through the next poll, plus before-and-after views of the same library/query if an item becomes visible sooner. Identify the build or commit.
  • Admin autoscan API (internal/autoscan/repository.go): before-and-after excerpts of the same response showing last_run_at before and after a reset, with the build or commit identified.

Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness.

Posted via Macroscope — Visible change evidence

Comment thread internal/autoscan/repository.go Outdated
Comment on lines +252 to +253
u.Path = strings.TrimRight(u.Path, "/")
u.RawPath = ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug high

URL normalization collisions occur when clearing RawPath makes distinct URLs such as http://host/a%2Fb and http://host/a/b compare equal, even though the resolver forwards their original escaped paths to the plugin. Preserve escaped path distinctions during normalization so edits between separately routed upstreams do not retain the old continuation token and let AdvanceMarker accept an old-upstream poll result.

Prompt for LLM

File internal/autoscan/repository.go:

Line 252 to 253:

URL normalization collisions occur when clearing RawPath makes distinct URLs such as http://host/a%2Fb and http://host/a/b compare equal, even though the resolver forwards their original escaped paths to the plugin. Preserve escaped path distinctions during normalization so edits between separately routed upstreams do not retain the old continuation token and let AdvanceMarker accept an old-upstream poll result.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch, fixed in 7f7bfeb: the comparison now trims the trailing slash from both Path and RawPath instead of dropping RawPath, so /a%2Fb and /a/b stay different. There's a test for both.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks for fixing it. Preserving RawPath keeps /a%2Fb distinct from /a/b, and the regression test covers the reported collision. Based on your description of 7f7bfeb59, the original finding is addressed.

if _, err := tx.Exec(ctx, `
UPDATE autoscan_sources
SET marker = NULL, updated_at = now()
SET marker = NULL, last_run_at = NULL, updated_at = now()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Bug medium

Incomplete last_run_at resets exclude bound sources whose marker is already NULL, including sources whose initial poll failed and recorded a recent run time, leaving them waiting out their interval after an upstream change. Remove the marker-presence predicate so every bound source receives the scheduling reset.

SET marker = NULL, last_run_at = NULL, updated_at = now()
			WHERE connection_id = $1
Prompt for LLM

File internal/autoscan/repository.go:

Line 206:

Incomplete last_run_at resets exclude bound sources whose marker is already NULL, including sources whose initial poll failed and recorded a recent run time, leaving them waiting out their interval after an upstream change. Remove the marker-presence predicate so every bound source receives the scheduling reset.

Suggested Code:

SET marker = NULL, last_run_at = NULL, updated_at = now()
			WHERE connection_id = $1

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

​

​

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, fixed in 7f7bfeb. The reset now also clears last_run_at for bound sources that have no marker but have run, such as one whose first poll failed, so it retries the new URL at the next cycle. The connection reset test now includes such a source.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thanks—that addresses the reported gap. Clearing last_run_at regardless of whether a bound source has a marker lets a source whose initial poll failed retry the changed upstream at the next cycle. Including that case in the connection reset test covers the regression.

… apart

A connection URL change skipped bound sources that had no marker, such
as one whose first poll failed, so after fixing the URL that source
still waited out its interval. Clear last_run_at for those too.

Clearing RawPath made an escaped slash (%2F) compare equal to a path
separator. Trim the trailing slash from both forms instead.
@silo-kody

silo-kody Bot commented Oct 8, 2026 •

Copy link
Copy Markdown

Silo Kody — review complete

Review finished. Check the inline comments for findings and verify each suggestion against the code and tests.

Reviewing changes in Silo
  • Include the related issue, expected behavior, and validation steps in the PR description.
  • For API changes, describe the effect on Apple and Android clients and Jellyfin compatibility.
  • For plugin changes, identify the affected SDK contract, plugin, and catalog entry.
  • Follow this repository's AGENTS.md and CONTRIBUTING.md.
  • Request another review with @kody start-review in a PR comment.
  • React with 👍 or 👎 to give feedback on individual suggestions.
Review settings
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug ✅
Performance ✅
Security ✅
Business Logic ❌
⚠️ 1 Kody Rule(s) were not evaluated

These rules declare that they need repository context beyond this diff, and the review could not retrieve it, so they were not judged on this pull request:

  • Preserve optional store capabilities through production wrappers

@macroscopeapp

macroscopeapp Bot commented Oct 8, 2026

Copy link
Copy Markdown

The latest commits add two user-visible cases: a connection change now clears last_run_at for a source whose first poll failed, so it retries on the next cycle; and switching between an escaped-path URL and a path-separator URL now resets the upstream marker. The Evidence section still contains no captures or API excerpts, and its database-test reference does not show these outcomes. The PR explains that a running capture was unavailable, but these newly changed cases are not demonstrated.

Suggested fix: add before-and-after admin source-row captures and matching API excerpts for the failed-first-poll reset, plus a recording or same-query before-and-after showing pickup after an escaped-path upstream switch. Identify the build or commit, and include desktop and phone-width captures if the status change is visible at both widths.

Automated check: Macroscope check run agent (gpt-6-luna). Evidence was not reviewed for correctness.

Posted via Macroscope — Visible change evidence

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

impact: polish Annoyance, cosmetic, or nice-to-have priority: P2 Limited scope, workaround exists, or polish

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants