bug#81091: a: package-selected-packages not updated after init

Philip Kaludercic <[email protected]> Sun, 02 Aug 2026 11:46:30 +0000
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
Philip Kaludercic <[email protected]> writes:

> Cosimo Agati <[email protected]> writes:
>
>> Hello everyone,
>>
>> I noticed some unusual behavior in Emacs, but I'm not sure whether
>> it's a bug.  The issue I'm about to describe is present in the
>> very latest Emacs Git master, started with 'emacs -Q' at the time
>> of writing.
>>
>> Background: in my init file, I have a bunch of invocations to
>> package-install to install packages during Emacs initialization if
>> not present.
>>
>> I noticed that the 'package-selected-packages' variable is not
>> updated properly after initialization (it seems to be nil no
>> matter what from what I've seen), although the packages do get
>> installed in practice.
>>
>> I looked into package.el and found that, indeed, there are some
>> caveats when setting 'package-selected-packages' during init time:
>> function package--save-selected-packages (which is called by
>> package-install and package-delete) works as follows:
>>
>> (defun package--save-selected-packages (&optional value)
>>   "Set and save `package-selected-packages' to VALUE."
>>   (when (or value after-init-time)
>>     ;; It is valid to set it to nil, for example when the last package
>>     ;; is uninstalled.  But it shouldn't be done at init time, to
>>     ;; avoid overwriting configurations that haven't yet been loaded.
>>     (setq package-selected-packages (sort value #'string<)))
>>   (if after-init-time
>>       (customize-save-variable 'package-selected-packages package-selected-packages)
>>     (add-hook 'after-init-hook #'package--save-selected-packages)))
>>
>> The comment seems to suggest this: the actual customization should
>> *not* be applied to the configuration file(s) until after
>> initialization is complete.
>>
>> This is a reasonable rationale, but I believe that the code,
>> as-is, is flawed: during initialization, customize-save-variable
>> is *not* called, instead, a hook is added to "delay" the actual
>> write to the custom file until after initialization is complete.
>>
>> Unfortunately, when that happens, package--save-selected-packages
>> is called with no arguments, meaning that the 'value' parameter is
>> nil.  This leads to 'package-selected-packages' being saved as nil
>> in the end.
>
> That is what the after-init-time check should catch.
>
>> If my interpretation of the intent of the existing code and my
>> reasoning so far is correct (if not, please correct me!), I
>> propose the following patch:
>>
>> modified   lisp/emacs-lisp/package.el
>> @@ -1834,16 +1834,15 @@ package--find-non-dependencies
>>               unless (memq name dep-list)
>>               collect name)))
>>  
>> +(defun package--apply-selected-packages ()
>> +  (customize-save-variable 'package-selected-packages package-selected-packages))
>> +
>>  (defun package--save-selected-packages (&optional value)
>>    "Set and save `package-selected-packages' to VALUE."
>> -  (when (or value after-init-time)
>> -    ;; It is valid to set it to nil, for example when the last package
>> -    ;; is uninstalled.  But it shouldn't be done at init time, to
>> -    ;; avoid overwriting configurations that haven't yet been loaded.
>> -    (setq package-selected-packages (sort value #'string<)))
>
> The crux of the issue is here, when invoked from `after-init-hook', we
> know that value is nil and after-init-time is non-nil, meaning that we
> reset package-selected-packages to (sort nil #'string<) i.e. nil.  In my
> mental evaluation model, I could imagine that
>
> diff --git a/lisp/emacs-lisp/package.el b/lisp/emacs-lisp/package.el
> index 062659fc912..4f397721534 100644
> --- a/lisp/emacs-lisp/package.el
> +++ b/lisp/emacs-lisp/package.el
> @@ -1854,9 +1854,11 @@ package--save-selected-packages
>    "Set and save `package-selected-packages' to VALUE."
>    (when (or value after-init-time)
>      ;; It is valid to set it to nil, for example when the last package
> -    ;; is uninstalled.  But it shouldn't be done at init time, to
> -    ;; avoid overwriting configurations that haven't yet been loaded.
> -    (setq package-selected-packages (sort value #'string<)))
> +    ;; is uninstalled.  But it shouldn't be done at init time, to avoid
> +    ;; overwriting configurations that haven't yet been loaded.  We fall
> +    ;; back to the default value of `package-selected-packages' when
> +    ;; this function is invoked by `after-init-hook'.
> +    (setq package-selected-packages (sort (or value package-selected-packages) #'string<)))
>    (if after-init-time
>        (customize-save-variable 'package-selected-packages package-selected-packages)
>      (add-hook 'after-init-hook #'package--save-selected-packages)))

Of course, this doesn't work, since this breaks if we call

  (package--save-selected-packages nil)

to set the set of saved packages to nil, it won't work.

>
> could be enough to fix this, since in that case we fall back to default
> value of package-selected-packages.  This shouldn't break the other
> cases we have to be concerned about either, since we will still reset
> the value of the list to nil when the last package is deleted.
>
> Another idea would be to drop the optional argument and always have the
> function operate on the dynamic value of `package-selected-packages'.
>
> I would prefer either of these options before we add another top-level
> function tbh.

Re-thinking it, I think your approach is the best.  I have pushed a
change to the emacs-31 branch along the lines of your suggestion.  I'll
therefore close the bug now.  Thanks for your help!

>> +  (setq package-selected-packages (if value (sort value #'string<) nil))
>                                      ^
>                                      why do you add this check?
>
> While on this topic, why do we sort the list every time we amend the
> list, and not just when storing the value?
>                                      
>>    (if after-init-time
>> -      (customize-save-variable 'package-selected-packages package-selected-packages)
>> -    (add-hook 'after-init-hook #'package--save-selected-packages)))
>> +      (package--apply-selected-packages)
>> +    (add-hook 'after-init-hook #'package--apply-selected-packages)))
>>  
>>  (defun package--user-selected-p (pkg)
>>    "Return non-nil if PKG is a package was installed by the user.
>>
>> This patch was by no means thoroughly tested, but it appears to
>> fix the issue after some quick and dirty examples.  I tried with
>> the following init files from a clean Emacs Git build (excluding
>> custom section):
>>
>> file 1:
>> (package-refresh-contents)
>> (package-install 'auctex)
>> (package-install 'altcaps)
>>
>> file 2 (used after manually installing packages auctex and
>> altcaps):
>> (package-refresh-contents)
>> (package-delete (package-get-descriptor 'altcaps))
>> (package-delete (package-get-descriptor 'auctex))
>>
>> In both cases, 'package-selected-packages' was updated exactly as
>> expected, even when the packages were installed/removed
>> non-interactively during initialization.  This did not work
>> properly with the original code.
>>
>> What do you think?  Is there something I'm missing from the
>> original code or is this a genuine bug?
>
> I certainly agree that there is an edge case here, and I think we have
> correctly identified it, the question is really just how we want to
> approach it.
>
>> Thank you for your attention.