You pushed tag v1.3.0 of a PHP library on Packagist, but consumers' composer update does not offer 1.3.0; what do you check?
answer
- did Packagist re-crawl at all
- hook from the code host
- Skipped tag, only with -vvv
- version key or tag format
- consumer's constraint and stability
basics
~20 sFirst check whether Packagist re-read the repository, usually a missing hook. Then check whether the tag was skipped for an invalid name, a mismatched version key or missing composer.json. Last, check the consumer's constraint, stability settings and repository order.
solid answer
~40 sWork from the registry outwards. **Did Packagist see the push?** Without a hook from the code host, Packagist only re-reads the repository on a later crawl or a manual update, so the package page still lists the old versions. **Did it import the tag?** Tags are skipped silently when the name does not parse as a version, when it carries a `dev` suffix, when `composer.json` at that commit is missing or invalid, when a hard-coded `version` key disagrees, or when another tag already normalises to the same version. Reading the repository locally with Composer in `-vvv` mode prints `Skipped tag v1.3.0, ...` with the reason. **Can the consumer take it?** Their constraint may exclude it (`~1.2.0`), a pre-release suffix needs a lower stability, another package may conflict, or a higher-priority repository may shadow Packagist.
code
bash · 4 lines# In a scratch project that reads the library straight from its repository
composer config repositories.lib vcs https://github.com/mira/date-phrases
composer show mira/date-phrases --all -vvv 2>&1 | grep -i 'tag'
# Skipped tag v1.3.0, tag (1.3.0.0) does not match version (1.2.0.0) in composer.jsongo deeper
Know that Packagist must re-read the repository after a push, and that the consumer's constraint decides which versions are allowed.
Walk the three stages: Packagist update through the hook, tag import rules, and the consumer's constraint and stability.
Diagnose from evidence: the package page, Composer's -vvv Skipped tag lines, and the resolver's inputs, instead of clearing caches and retagging.
Build release hygiene into the pipeline: validate before tagging, a verified hook, and a post-release check that the version is visible.
## Three places a release can get lost A new version travels from your repository through Packagist to a consumer's resolver. When `composer update` does not offer it, the version is stuck at one of three points, and checking them in order avoids guessing. | Stage | Question | Typical cause | |---|---|---| | 1. Packagist update | Did Packagist re-read the repository? | no hook, or a failing one | | 2. Import | Did the tag become a version? | invalid name, version mismatch, broken `composer.json` | | 3. Resolution | Can the consumer's project select it? | constraint, stability, conflict, repository order | ## Stage 1: Packagist has not re-read the repository Packagist indexes the repository and does not watch it continuously. The standard setup is a **hook**: the code host notifies Packagist whenever you push, and Packagist re-crawls within moments. When the hook is missing, removed or failing, Packagist only catches up on a later crawl or when a maintainer triggers an update by hand from the package page. The quick test is the package page on packagist.org. If `1.3.0` is not listed there, no consumer can see it, whatever they do. ## Stage 2: the tag was read but skipped Packagist imports tags using Composer's rules for VCS repositories, and a skipped tag does not show up as an error on the consumer's side. Common reasons: - **The name does not parse as a version.** `v1.3.0` and `1.3.0` are fine; `1.3.0-final` or `release/1.3` are not. - **A `dev` suffix.** Tags cannot use dev prefixes or suffixes. - **A hard-coded `version` key.** If `composer.json` still says `"version": "1.2.0"`, the tag `v1.3.0` does not match and is skipped. - **No readable `composer.json` at that commit,** for example invalid JSON committed just before tagging. - **A duplicate.** If `1.3` already exists, `1.3.0` normalises to the same `1.3.0.0` and is dropped. To see the reason, point Composer at the repository directly. Add it as a `vcs` repository in a scratch project and run a command that reads it with `-vvv`. Composer prints lines such as `Skipped tag v1.3.0, tag (1.3.0.0) does not match version (1.2.0.0) in composer.json`. `composer validate` in the library before tagging catches the `version` key and most JSON errors in advance. ## Stage 3: the consumer cannot select it When `1.3.0` is on Packagist but a consumer still gets `1.2.x`, the resolver is choosing correctly given its inputs: 1. **The constraint excludes it.** The consumer's version constraint may stop below `1.3.0` on purpose. 2. **Stability.** A tag such as `v1.3.0-RC1` has stability `RC`, which the consumer's stability settings may not accept. 3. **Another package conflicts.** A different dependency may require `<1.3`. The resolver keeps the version that satisfies everything. 4. **Repository order.** A private repository listed above Packagist that also contains the package name wins, because Composer 2 repositories are canonical, and it may not have `1.3.0` yet. 5. **A partial update.** `composer update other/package` only touches the named packages, so the library keeps its locked version. Composer's metadata cache is rarely the problem. It revalidates Packagist metadata with `If-Modified-Since` on each run, so once Packagist lists a version, the next `update` sees it. ## A worked example The maintainer of `mira/date-phrases` tags `v1.3.0`, and a user reports that `composer update` still installs `1.2.4`: 1. The Packagist page lists versions up to `v1.2.4` only, so stage 1 or 2 is the problem. 2. Triggering an update of the package by hand changes nothing, so Packagist is reading the repository; the tag is being rejected. 3. Reading the repository locally with `-vvv` prints `Skipped tag v1.3.0, tag (1.3.0.0) does not match version (1.2.0.0) in composer.json`. 4. The cause is a `"version": "1.2.0"` key that a merge brought into `composer.json` shortly before the tag. 5. The fix is to remove the key, tag `v1.3.1`, and leave `v1.3.0` alone, since no consumer can have locked it. ## Preventing it - Configure the hook when you first publish, and check the package page after the first tag. - Run `composer validate` in the release checklist, and never keep a `version` key in a published library. - Tag exactly `vMAJOR.MINOR.PATCH`, with a pre-release suffix only when you mean one.
- Why does a leftover version key in composer.json hide every new tag?When `composer.json` carries `version`, Composer uses it instead of the tag and then compares the two. Any tag whose normalised version differs, which is every new tag once the key is stale, is skipped as not matching. Removing the key lets the tag supply the version again.
- The repository has tags 1.3 and 1.3.0 on different commits. What does Packagist offer?Both normalise to `1.3.0.0`, so only one becomes a version; the other is skipped as conflicting, with a `-vvv` message naming the tag it clashed with. Which one wins depends on the order the tags are read, so the fix is to delete the stray tag before anyone locks it and to tag one format consistently.
saying these in an interview costs you the question
- Clearing Composer's cache is the first fix for a missing release.
- Packagist reports skipped tags as errors to consumers.
- A tag named 1.3.0-final is imported as version 1.3.0.
- Pushing a tag is enough; Packagist watches every repository continuously.
- Deleting and re-pushing the same tag is the standard fix.