HeyPuter/puter · warning · HttpError
subdomain_not_owned
subdomain_not_owned
Error message
Subdomain not owned by user
What it means
Thrown by `#ensurePuterSiteSubdomainIsOwned` when the index_url points at a puter.site subdomain whose `subdomain` row is either absent (even after a primary-store fallback check) or owned by a different user. Returns HTTP 400 with legacyCode `subdomain_not_owned`, carrying `fields.subdomain`.
Source
Thrown at src/backend/drivers/apps/AppDriver.js:1205
}
}
async #ensurePuterSiteSubdomainIsOwned(indexUrl, user) {
if (!user) return;
const subdomain = this.#extractPuterHostedSubdomain(indexUrl);
if (!subdomain) return;
let row = await this.stores.subdomain.getBySubdomain(subdomain);
if (!row) {
// Deploys create the subdomain and immediately point the app
// at it, so a replica or peer-cache miss here would wrongly
// refuse the owner. Confirm against the primary before failing.
row = await this.stores.subdomain.getBySubdomain(subdomain, {
primary: true,
});
}
if (!row || row.user_id !== user.id) {
throw new HttpError(400, 'Subdomain not owned by user', {
legacyCode: 'subdomain_not_owned',
fields: { subdomain },
});
}
}
/**
* Origin-bootstrap detection: rows auto-created when an unknown origin
* first needed an app row (no human-supplied metadata). Marker is `name ===
* uid && title === uid` and a description starting with "App created from
* origin ". Only these rows are eligible for same-owner merging — refusing
* to merge arbitrary same-owner apps prevents accidental data loss.
*/
#isOriginBootstrapApp(app) {
if (!app || typeof app !== 'object') return false;
if (typeof app.uid !== 'string' || !app.uid) return false;
if (app.name !== app.uid) return false;
if (app.title !== app.uid) return false;View on GitHub (pinned to 908ec23eda)
Solutions
- Deploy to a subdomain you own.
- Register/own the target subdomain first, then point the app at it.
- If the deploy just created it, retry once — the primary fallback should resolve it; if not, verify the subdomain name.
Example fix
// before
object.index_url = 'https://someoneelse.puter.site/';
// after
object.index_url = `https://${mySubdomain}.puter.site/`; Defensive patterns
Strategy: validation
Validate before calling
const sub = extractPuterSubdomain(indexUrl); // e.g. 'foo' for https://foo.puter.site
if (sub) {
const row = await stores.subdomain.getBySubdomain(sub);
if (!row || row.user_id !== user.id) throw new Error('subdomain not owned');
}
await driver.create({ object: { index_url } }); Try / catch
try {
await driver.create({ object: { index_url } });
} catch (e) {
if (e.code === 'subdomain_not_owned') { index_url = `https://${mySub}.puter.site/`; /* retry once */ return; }
throw e;
} Prevention
- Only point apps at puter.site subdomains you own.
- Register the subdomain before referencing it in index_url.
- On a fresh deploy, retry once to let the primary store catch up.
When it happens
Trigger: Creating/updating an app whose index_url uses a `<sub>.puter.site` host the acting user doesn't own. The store first checks replica/cache, then the primary store, to avoid false negatives during deploys.
Common situations: Pointing an app at another user's deployed puter.site subdomain; a stale cache miss on a freshly created subdomain during a deploy; typo in the subdomain name.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/a29da6398e871828.
Report an issue: GitHub.