Troubleshooting

The most common ways a VelvetPress pipeline breaks, and how to unstick it. Every entry follows the same shape: symptom → why it happens → what to do, with a concrete next action at the end so you know exactly which button to click.

For wire-level details — endpoint shapes, auth headers, response codes — see Update manifest reference. For the happy-path walkthroughs, see Publishing a plugin or theme and Installing on a customer WordPress site.

I pushed but no release appeared

The product's Pipeline row in the dashboard still shows the previous commit, or is blank. Two causes account for almost every case:

  • The GitHub App isn't installed on the account that owns the repo. VelvetPress only sees pushes from repos that the App has access to. If you transferred the repo, switched orgs, or removed the App's repo access, deliveries stop.
  • You pushed to the wrong branch. Only pushes to the configured release branch trigger a build. Pushes to feature branches and tag pushes are ignored.

What to do. Open the product, look at the Pipeline row, then walk GitHub's webhook delivery log to see whether the push even reached the backend. The full three-step diagnosis — pipeline state, Settings → Webhooks → Recent Deliveries, configured branch — is in Publishing a plugin or theme → "I pushed but no release appeared". You can re-deliver any failed webhook from GitHub without making a new commit.

Plugin says "Invalid API key"

The status badge on Settings → VelvetPress in wp-admin flips to ✗ Error with an "invalid API key" message right after you save the key. The backend matched the key to a site row but rejected the request. Two common causes:

  • The plugin is pointing at a different environment than the key was issued for. Keys are scoped per environment. A key minted on the prod dashboard won't validate against a dev backend, and vice versa. The client plugin defaults to https://api.velvetpress.dev; if you've set VELVETPRESS_API_URL in wp-config.php to point somewhere else, the key has to match that backend.
  • The domain you're serving from isn't on the key's allowed list. The backend verifies the incoming domain (taken from home_url()) is one of the domains the developer registered for this key. example.com, www.example.com, and staging.example.com are three different domains to VelvetPress.
  • The key was revoked or regenerated. If the developer deleted the site row and recreated it, the old key is dead and a new one was issued.

What to do. Open Settings → VelvetPress and expand Debug Info to see the exact domain the plugin is reporting. Send that string to the developer and ask them to confirm it matches a domain on this site's allowed list. If they regenerated the key, they need to send you the new one — paste it into the API Key field and click Save Changes to re-register.

WordPress doesn't see the new version after a successful build

The dashboard shows the product's Pipeline as ✓ Built with the new version, but wp-admin still shows the old version. Nothing is broken — WordPress is just on its own clock.

The Plugin Update Checker library polls VelvetPress roughly every twelve hours, plus whenever you load Dashboard → Updates. Until that next poll runs, WordPress hasn't asked the backend about the new release, so it doesn't know to offer it.

What to do. In wp-admin, go to Dashboard → Updates and click Check Again at the top of the page. That forces WordPress to re-poll every update source, including VelvetPress, immediately. The new version shows up in the Plugins or Themes section with an Update Now button. (See Installing on a customer WordPress site for the full walkthrough of what the update flow looks like.)

"Update Now" button fails with "Download failed"

WordPress shows the new version on Dashboard → Updates and you click Update Now, but the upgrade screen reports "Download failed" or "Couldn't connect to the server."

The download_url in the manifest is a short-lived URL that expires five minutes after WordPress fetched the manifest. If more than five minutes passed between the last poll and your click, the URL was already stale when WordPress tried to use it.

The other common cause is outbound network filtering — some managed hosts block direct downloads from the storage host.

What to do. Click Check Again on Dashboard → Updates to re-fetch a fresh manifest (and a fresh five-minute URL), then click Update Now within a minute or two. If it still fails, your host is probably filtering outbound HTTPS to the download host — ask them to allow it.

I'm seeing the wrong product name in WP admin

The plugin or theme shows up in wp-admin with a name that doesn't match what you expected — often the URL-style slug (vp-starter-theme) instead of the friendly name (VP Starter Theme).

The build worker reads the display name from the WordPress header at build time: Theme Name: in style.css for themes, Plugin Name: in the main plugin PHP file for plugins. If the header is missing, empty, or unparseable, the worker falls back to the product slug so the release still publishes.

What to do. Open your release branch and confirm the header on the right file:

  • Themes: style.css must include a line like Theme Name: VP Starter Theme.
  • Plugins: the main plugin file (matching your slug, e.g. velvetpress.php) must include Plugin Name: VelvetPress.

Fix the header, bump the version, push. The next release manifests with the corrected name, and the new name shows up in wp-admin after the next update.

Can I run themes or plugins that need a build step?

Yes — and you don't have to configure anything. The build worker auto-detects build configs at the repo root:

  • If package.json is present, the worker runs npm install followed by npm run build before zipping. Configure your bundling in npm run build exactly as you would locally.
  • If composer.json is present, the worker runs composer install --no-dev so production dependencies are vendored into the zip.
  • If neither file is present, the worker skips the build phase and zips the source as-is.

Both detection paths are independent — a repo that ships both gets both build steps. Each step has a five-minute timeout per command; if your build is slower than that, it'll fail.

The zip itself excludes development noise so what lands on customer sites stays small. Patterns dropped before the archive is finalized: .git/, .github/, node_modules/, src/, *.lock, .eslintrc, .prettierrc, and .editorconfig. If your plugin's PHP lives under src/ (a common Composer-style layout), rename that directory — e.g. to includes/ — or the zip will ship without your code.

What to do. Make sure npm run build works from a clean checkout locally (rm -rf node_modules && npm install && npm run build). If it works on your machine but fails in the worker, the most common cause is a missing build-time env var — the worker runs without your local shell, so process.env.* reads anything outside the repo come back empty.

What's next

  • Installing on a customer WordPress site — the customer-side guide. Section "Troubleshooting" at the end covers the WordPress-admin-only failures (activation errors, Checking… hangs, domain mismatch) in more depth.
  • Publishing a plugin or theme — the developer-side guide. The end of that page has a longer "I pushed but no release appeared" diagnosis path.
  • Update manifest reference — the exact wire format of every endpoint involved here, with curl examples you can paste to confirm whether the backend or the WordPress side is at fault.