Re: markdown-ts-mode not recognized after updating built-in packages

Rahul Martim Juliato <[email protected]> Sun, 28 Jun 2026 14:20:00 -0300
Newsgroups gmane.emacs.help
Message-ID <[email protected]>
Stéphane Marks <[email protected]> writes:

> On Fri, Jun 26, 2026 at 5:55 AM Ashish Panigrahi <[email protected]>
> wrote:
>
>> Hi,
>>
>> I noticed recently that after updating the built-in packages (emacs
>> 31.0.90), opening a markdown file with the built-in markdown-ts-mode
>> doesn't not enable the major mode. An echo area message appears
>> mentioning to uninstall the package since it is built-in since emacs 31.
>>
>> This is not an issue with the corresponding tagged releases of the
>> built-in packages when installing emacs 31.0.90 from scratch. I also
>> have an `:ensure nil` keyword in my use-package macro for
>> markdown-ts-mode.
>>
>> Is this behaviour expected? Thanks.
>
>
> Yes.  The MELPA release has been deprecated for Emacs 31+ since this commit
> https://github.com/LionyxML/markdown-ts-mode/commit/801579b9b955f63673dd6dc9742c1fd5311b76c9
>
> Perhaps someone I cc'd (or someone else) can recommend a good/best way to
> handle sharing a configuration across pre 31 and 31+ where 31+ will use the
> built-in package and pre 31 from your 'package-user-dir'.

Hi,

As Stéphane already pointed out, this is expected behaviour: the MELPA
release deliberately refuses to load on Emacs 31+, since
markdown-ts-mode is built in from Emacs 31 on. It's documented here:

  https://github.com/LionyxML/markdown-ts-mode#if-you-still-opt-to-use-this-package

That deprecation guard is what prints the message you see in the echo
area.

I separated in topics some possible problems/solutions:


What is most likely happening on user side
------------------------------------------

The built-in package is being shadowed by a copy of the deprecated MELPA
release sitting in your elpa/ directory. Once such a copy is present,
package.el activates it and puts its directory ahead of the built-in on
load-path, so the deprecated stub is what gets loaded.

This could possibly sneak in via an upgrade: `M-x package-upgrade' lists
built-in packages as candidates too, and since the deprecated MELPA
release still carries a high (date-based) version, package.el sees it as
a newer version than the built-in 1.0 and "upgrades" the built-in by
installing the stub into elpa/. From then on the built-in is shadowed.
(`package-upgrade-all' does not touch built-ins by default, but a manual
`package-upgrade', or an install left over from Emacs 30, has the same
effect.)


Recommended fix (works with one shared config and one shared elpa/)
-------------------------------------------------------------------

Tell package.el to ignore the elpa/ copy on Emacs 31, so the built-in
wins. With `package-load-list' you can disable just that package.

This must go in your early-init.el, because packages are activated
between early-init.el and init.el -- setting it in init.el would be too
late, the elpa/ copy would already be active:

  ;; early-init.el
  (when (>= emacs-major-version 31)
    (add-to-list 'package-load-list '(markdown-ts-mode nil)))

This way:
  - the built-in is used on Emacs 31;
  - the elpa/ copy is left in place, so Emacs 30 keeps using the MELPA
    package as before (no need to delete anything);
  - because the package is "disabled", a later `package-upgrade' can't
    quietly bring the stub back.


Configuration
-------------

On Emacs 31 the expected configuration is something like the following
(opt-in, since the built-in version is still experimental). Note the
explicit load: the built-in file currently has no ;;;###autoload cookie,
so the `markdown-ts-mode' command does not exist until the file is
loaded, and a bare auto-mode-alist entry gives you "Ignoring unknown
mode 'markdown-ts-mode'":

  (use-package markdown-ts-mode
    :ensure nil
    :mode ("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'")
    :init (load-library "markdown-ts-mode"))

A single configuration shared between Emacs 30 (the MELPA package) and
Emacs 31 (the built-in, revamped and feature-full package). The two
pieces go in different files:

  ;; early-init.el -- disable the elpa/ copy on 31 so the built-in wins
  (when (>= emacs-major-version 31)
    (add-to-list 'package-load-list '(markdown-ts-mode nil)))

  ;; init.el
  (if (>= emacs-major-version 31)
      ;; Built-in: no autoload cookie -> force-load so the command exists.
      (use-package markdown-ts-mode
        :ensure nil
        :mode ("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'")
        :init (load-library "markdown-ts-mode"))
    ;; Emacs <31: not built-in -> pull from MELPA (it ships autoloads).
    (use-package markdown-ts-mode
      :ensure t
      :mode ("\\.md\\'" "\\.mdx\\'" "\\.markdown\\'")))


Other ways to deal with the shadowing
-------------------------------------

  - Just delete the elpa/ copy: M-x package-delete RET markdown-ts-mode
    RET. This only removes the MELPA copy from package-user-dir, never
    the built-in. The drawback is that a later upgrade can reinstall it.

  - Give each Emacs version its own package directory (this also avoids
    .elc incompatibilities between versions), set in early-init.el
    (again, before packages are activated):

      ;; early-init.el
      (setq package-user-dir
            (locate-user-emacs-file (format "elpa-%d" emacs-major-version)))

    Emacs 30 then installs into elpa-30/ and Emacs 31 uses elpa-31/
    (without the MELPA package), so the built-in wins.

Hope this helps.

Best,

-- 
Rahul Martim Juliato