<!--
Source: https://app.clickup.com/t/869edzz7b
Export date: 2026-08-07
Rule: do not annotate inline; counter-points go into a separate file under spec/
-->

# Sever cloned license identity before WordPress calls remote

**Status:** in progress · **List:** Bugs · **Folder:** Real Product Manager · **Tags:** sprint-26-32

**Assignees:** Matthias Günter

---

_This issue was automatically generated by an MCP tool by copying_ [https://app.clickup.com/t/869chp8wu](https://app.clickup.com/t/869chp8wu)

**Noticed via** _(which indicator revealed the problem (e.g., link support tickets, comments from another ticket, ...)?)_:

- [Reamaze: Real Cookie Banner Pro license after WP Toolkit clone (Gabriel Smoljar / MetaShare)](https://devowl.reamaze.com/admin/conversations/real-cookie-banner-feature-request-b3b87cb4e143-9d179f8ec3567d08)
- [Reamaze: follow-up on staging vs production activation](https://devowl.reamaze.com/admin/conversations/real-cookie-banner-feature-request-b3b87cb4e143-3ac8d01311e1fab8)

**Issue** _(what is the observed problem and how does it become visible to developers or users?)_:

After copying a WordPress database from production to staging (e.g. via WP Toolkit / cPanel), Real Cookie Banner Pro can:

- report license quota errors,
- show the same client UUID on both environments,
- move production activations in the Customer Center to the staging license key,
- or deactivate production when staging reconnects to the license server.

Sites that activate the license programmatically via the [`DevOwl/RealProductManager/License/Programmatic/{slug}`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/9173d65292f35c14728357f90c69e2795a9dbb13/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L390-L437) filter are especially affected because remote license calls can run on `init`, before the admin-only hostname check on `shutdown`.

**Cause** _(what originally causes the problem (stack traces, Git blame, config, ...?)_:

A DB clone copies `rpm-wpc-*` / plugin-specific license options including the **client UUID** and activation code. Staging then talks to the license server as the same client as production.

Host-mismatch handling was introduced via Lizenzaktivierung berücksichtigt nur Hostname, aber nicht Pfad der WordPress Installation (Installation in Unterverzeichnis) ([https://app.clickup.com/t/f134wh](https://app.clickup.com/t/f134wh)) as [`validateNewHostName()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/07a8d78322b1279dd92197027de1fc8bed461f48/packages/real-product-manager-wp-client/src/license/License.php#L144-L175) (local deactivate + clear UUID on admin `shutdown`). That made sense when activation only happened through the admin UI.

Lizenzschlüssel programmatisch hinterlegen und aktivieren ([Pantheon.io](http://Pantheon.io), GIT-Synced Environments, Staging-Cloning) ([https://app.clickup.com/t/kbaqat](https://app.clickup.com/t/kbaqat)) added [`activateProgrammatically()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/9173d65292f35c14728357f90c69e2795a9dbb13/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L390-L437), which can call remote [`deactivate(true)`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/LicenseActivation.php#L162-L200) with the **cloned UUID** before `validateNewHostName()` runs. The same cloned identity is reused by [`syncWithRemote()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L217-L250), [`fetchRemoteStatus()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L255-L290), and telemetry [`PUT`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/client/TelemetryData.php). Worst case: staging sends `DELETE` for production's remote activation; production then drops its local activation on the next sync when the server returns `LicenseActivationNotFound`.

**Solution** _(how can the problem be solved? which other solutions were considered? ...)_:

Extract host-mismatch logic into `probablySeverClonedLicenseIdentity()` in [`License.php`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php) (local sever only: `deactivate(false)` + clear UUID; never remote `DELETE` with a cloned identity). Call it **before any remote license path** in the domain layer:

- [`License::syncWithRemote()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L217) / [`fetchRemoteStatus()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L255)
- [`LicenseActivation::activate()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/LicenseActivation.php#L77) and [`deactivate(true)`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/LicenseActivation.php#L162) (skip remote DELETE when identity was severed)
- [`TelemetryData`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/client/TelemetryData.php) PUT

[`validateNewHostName()`](https://git.owlinfra.de/devowlio/devowl-wp/-/blob/3cbc44788cfae9fa1417ad7f5c7992b46955c767/wordpress-packages/real-product-manager-wp-client/src/license/License.php#L292) delegates to the new helper and then re-runs programmatic activation. All existing skip guards are preserved (WP-CLI, redirects, IPs, `RPM_WP_CLIENT_SKIP_DYNAMIC_HOST_CHECK`, EC2/Azure/ELB/temp domains, `*.hostingersite.com` per [CU-869ea3vfe](https://app.clickup.com/t/869ea3vfe)).

Rejected alternatives: wrapping the HTTP client (too invasive), per-license client singleton refactor (out of scope), scattered one-off gates without a shared helper.

**Git commit message proposal**:

```
fix: sever cloned license identity before remote calls (CU-869edzz7b)
```
