#1149125 [apt] Packages metadata divergence causes repeated reinstalls with no diagnostic

Package:
apt
Source:
apt
Description:
commandline package manager
Submitter:
Ross Golder
Date:
2026-10-05 16:07:02 UTC
Severity:
normal
#1149125#5
Date:
2026-09-27 00:04:53 UTC
From:
To:
apt silently reinstalls the identical version on every invocation when
Packages metadata diverges from the state dpkg recorded, and gives no
diagnostic and no indication that this is not an upgrade.

Two independent situations each cause this on their own.

1. A Packages entry with Description continuation lines that are not
indented.

    Debian Policy 5.1 requires that continuation lines "must start with
a space or a tab", and 5.6.13 defines Description as a multiline field
whose extended description lines start with a space. Such a Packages
file is invalid. apt's parser does not report a parse error; instead
pkgTagSection::Scan looks for the terminating colon with memchr(Stop,
':', End - Stop), which searches the whole remaining buffer rather than
the current line. The unindented line is therefore taken as a new field
whose name runs up to the next colon, which silently absorbs the Depends
(or Pre-Depends, Conflicts, Breaks, Replaces) line that follows.

    debListParser::VersionHash() hashes Installed-Size, Depends,
Pre-Depends,
    Conflicts, Breaks and Replaces.

    With Depends absorbed, the hash of the Packages entry no longer
matches the hash computed from the same version in /var/lib/dpkg/status,
so pkgCacheGenerator::MergeListVersion does not merge them. Two distinct
pkgVersion records now exist for the identical version string.

2. A Packages entry that omits Installed-Size.

    Debian Policy 5.3 lists Installed-Size among the fields of
DEBIAN/control without marking it "(mandatory)", and 5.6.20 only defines
its meaning, so a repository that omits it violates no rule.
VersionHash() then skips the field, and apt compares an absent value
against the one dpkg recorded, with the same non-merge result as above.

    Effect in pkgDepCache::MarkInstall

      if (P.CandidateVer == Pkg.CurrentVer())   // apt-pkg/depcache.cc:1666

    compares pkgVersion records, not version strings. With two records
for the same version string the test fails, the package is not kept, and
it is marked for install.

Effect on the reported action

apt-pkg/pkgcache.cc:854, VerIterator::CompareVer, does not compare
version strings despite its name. It walks the package's version list
and returns 1 if the other record appears earlier in the list, -1
otherwise, and 0 only on record identity. Two records with the same
version string therefore always compare as either up or down, never
equal. That value becomes Status at apt-pkg/depcache.cc:2198, and
StateCache::Upgrade() is Status > 0, so
apt-private/private-output.cc:808 prints "The following packages will be
upgraded" and :953 counts it in "N upgraded".

Which of "upgraded" or "downgraded" is reported is therefore decided by
the arbitrary position of two duplicate records in the version list, and
is not meaningful. dpkg then prints "Unpacking foo (1.0) over (1.0)".

Impact

- The output is self-contradictory and gives the operator nothing to act
on. There is no "reinstall" wording and no indication of the diverging
field.

- The loop is unbounded. Every apt-get upgrade re-fetches and re-unpacks
  identical bytes. In the reporter's case 4 packages / 14.8 MB were
re-fetched and 20.9 MB churned per run, on every run.

- There is no supported way to diagnose it. Under --simulate the output
for a reinstall is structurally identical to that for a genuine upgrade:

       Inst foo [1.0] (1.0 origin)     <- reinstall of the same version
       Inst foo [1.0] (1.1 origin)     <- real upgrade

   The only cue is that the two version strings coincide. Enabling
   Debug::pkgCacheGen can expose this, but it is not documented and is
   unreachable to users without debug knowledge.

Proposed Solution

The fix is to make the existing, intentional cache-convergence behavior
visible and explained at default output level. This is a diagnosability
issue, not a change to what apt does.

A patch is attached implementing:

(a) ReInstallSameVersion() detection: Correctly identify packages where
version trings match but pointer-based comparison detects a difference —
this specific case indicates a metadata hash divergence.

(b) Reclassification: Exclude same-version reinstalls from the
Upgrade/Downgrade counts and display them in the "reinstalled" list instead.

(c) User-facing Notice: When same-version reinstalls are detected, emit
a plain-language message (at default output level, no flags needed)
explaining why it happens, that it will repeat until the repository is
fixed, and how to see per-field detail (Debug::pkgCacheGen=1).

The patch does not change what apt does — the reinstall behavior is
intentional and correct when metadata diverges (it allows the cache to
converge on local package rebuilds). It only makes the situation visible
and explained.

Related

Similar issue reported on the Pulp project (339 packages reinstalled per
run):
https://pulp.plan.io/issues/6982

#1149125#10
Date:
2026-10-05 15:55:03 UTC
From:
To:
Am Sun, Sep 27, 2026 at 07:04:53AM +0700, schrieb Ross Golder:

The bad-metadata-in-repository situation(s) you describe seem to be
indistinguishable from the cases this apt 'feature' is supposed to
act in. It is also not really detecting/explaining a relatively uncommon
differing-metadata-due-to-differing-implementations situation.

The first one is e.g. a Debian maintainer who builds the package locally
and installs it for final testing before uploading. The metadata in the
repository will/might be different if e.g. a dependency changed before
the archive has build the package and ships it. It is "the same
version", but with different metadata and yet apt will not repeatedly
reinstall ~ it will just "upgrade" to the version from the archive.

The second one e.g. happens if dpkg normalizes version numbers
differently (or at all) compared to the tools generating the metadata
for the repository. 0-Epochs are very fun in this regard for example.
The repeat will be eternal, but beside the repository software(s)
converging on the same normalisation, it might be faster to fix for the
package maintainer to normalize it in the package…

I suppose we could special-case multiline values in some way, so that at
least some cases of this could be detected, ~ if a line does not include
a colon ~, but they could of course e.g. for urls or versions with
epochs. So we relatively quickly enter the domain of verification which
might be too costly to do in general as the data should be fine™.

In an ideal world stuff like this never happens for users as it is
caught in testing by the repository maintainers. There might be a case
for some sort of linter tool from the apt-realm that could catch certain
problems (where stuff like doing previously said verification would be
not as costly as it is paid only once, not by potentially millions of
users). Such a tool (from us) doesn't exist as of yet.
on a clean system (assuming it has a tiny bit more than essential
dependencies) which should be the most obvious problem caused by an
absorbed Depends…

In practice that should not happen: Description sorts close to last in
dpkg/apt-ftparchive and below dependencies in dak, too. MultiArch sorts
below it in dak it seems through, which is another field that has to
match.


Which is a new list that you added, not an existing list like this
wording suggests, so that might not change what apt does on a system
level, but drastically changes the presentation and therefore might
change how users (and other tools) react.
completely ignores that possibility and assumes another reason.


So, in summary, I am not against showing those same-but-different
automatic reinstalls, but I think that also includes
partly-installed-or-very-bad-failures-requiring-a-reinstall. Together
they might not mix well with the user-triggered --reinstall ones
although all of them might be uncommon and somewhat similar enough to
warrant stuffing them all into the same list.

I do think trying to show a multi line notice message is (trying to)
over-explaining it through, especially if it is wrong in the more
common cases (still rare, but common sense dictates that "normal" usage
is more common than buggy metadata that the provider doesn't test).


The patch is a little … sus. Beside odd styling and dead code (e.g.
DepFieldNames) the test seems to show that neither your message nor
the new list is actually shown… (plus, that using testsuccessempty in
this way is both disturbing and somewhat clever… but not how that is
supposed to be used).


Best regards

David Kalnischkies

P.S.: It is common courtesy to indicate the usage of AI, especially if
the message text makes it somewhat obvious by its style. Bonus points
for including the prompt(s) in your prompt (aka bug report).