laurent22/joplin · error · Error
href ${href} not in baseUrl ${baseUrl} nor relativeBaseUrl $
Error message
href ${href} not in baseUrl ${baseUrl} nor relativeBaseUrl ${relativeBaseUrl} What it means
Thrown by hrefToRelativePath_() when converting a PROPFIND href into a sync-relative path. It tries three matches: the href starts with baseUrl, with relativeBaseUrl, or their percent-decoded forms. If none match, the href doesn't belong to the configured sync root and the driver can't map it, so it aborts to avoid silently mis-addressing files.
Source
Thrown at packages/lib/file-api-driver-webdav.js:113
return result.items;
};
return await basicDelta(path, getDirStats, options);
}
// A file href, as found in the result of a PROPFIND, can be either an absolute URL or a
// relative URL (an absolute URL minus the protocol and domain), while the sync algorithm
// works with paths relative to the base URL.
hrefToRelativePath_(href, baseUrl, relativeBaseUrl) {
let output = '';
if (href.indexOf(baseUrl) === 0) {
output = href.substr(baseUrl.length);
} else if (href.indexOf(relativeBaseUrl) === 0) {
output = href.substr(relativeBaseUrl.length);
} else if (decodeURIComponent(href).indexOf(decodeURIComponent(relativeBaseUrl)) === 0) {
output = decodeURIComponent(href).substring(decodeURIComponent(relativeBaseUrl).length);
} else {
throw new Error(`href ${href} not in baseUrl ${baseUrl} nor relativeBaseUrl ${relativeBaseUrl}`);
}
return rtrimSlashes(ltrimSlashes(output));
}
statsFromResources_(resources) {
const relativeBaseUrl = this.api().relativeBaseUrl();
const baseUrl = this.api().baseUrl();
const output = [];
for (let i = 0; i < resources.length; i++) {
const resource = resources[i];
const href = this.api().stringFromJson(resource, ['d:href', 0]);
const path = this.hrefToRelativePath_(href, baseUrl, relativeBaseUrl);
// if (href.indexOf(relativeBaseUrl) !== 0) throw new Error('Path "' + href + '" not inside base URL: ' + relativeBaseUrl);
// const path = rtrimSlashes(ltrimSlashes(href.substr(relativeBaseUrl.length)));
if (path === '') continue; // The list of resources includes the root dir too, which we don't want
const stat = this.statFromResource_(resources[i], path);
output.push(stat);View on GitHub (pinned to 2654b33620)
Solutions
- Make the configured WebDAV base URL exactly match the href prefix the server returns (same scheme, host, port, path).
- If behind a reverse proxy, fix the proxy to pass through the original host/path or adjust the base URL to the proxy-visible value.
- Ensure scheme consistency (use https if the server redirects to https).
- Decode/encode percent-encoded characters in the base URL to match the server's encoding.
Example fix
// before
if (href.indexOf(baseUrl) === 0) { output = href.substr(baseUrl.length); }
else if (href.indexOf(relativeBaseUrl) === 0) { output = href.substr(relativeBaseUrl.length); }
else if (decodeURIComponent(href).indexOf(decodeURIComponent(relativeBaseUrl)) === 0) { output = decodeURIComponent(href).substring(decodeURIComponent(relativeBaseUrl).length); }
else { throw new Error(`href ${href} not in baseUrl ${baseUrl} nor relativeBaseUrl ${relativeBaseUrl}`); }
// after - also tolerate scheme/port differences via URL parsing
const stripBase = (h, b) => { try { const H = new URL(h), B = new URL(b); if (H.pathname.indexOf(B.pathname) === 0) return H.pathname.slice(B.pathname.length); } catch {} return null; };
output = stripBase(href, baseUrl) ?? stripBase(href, relativeBaseUrl);
if (output === null) throw new Error(`href ${href} not under base ${baseUrl}`); Defensive patterns
Strategy: validation
Validate before calling
// Ensure the configured base URL matches the server's href prefix.
function baseUrlMatches(base: string, sampleHref: string): boolean {
try {
const B = new URL(base), H = new URL(sampleHref);
return H.pathname.indexOf(B.pathname) === 0;
} catch { return sampleHref.indexOf(base) === 0; }
}
if (!baseUrlMatches(configuredBaseUrl, observedHref)) {
throw new Error('Base URL does not match server href prefix; fix sync config.');
} Type guard
function hrefBelongsToBase(href: string, baseUrl: string, relativeBaseUrl: string): boolean {
return href.indexOf(baseUrl) === 0 || href.indexOf(relativeBaseUrl) === 0
|| decodeURIComponent(href).indexOf(decodeURIComponent(relativeBaseUrl)) === 0;
} Try / catch
try {
return await driver.list(path);
} catch (e) {
if (/not in baseUrl/.test(e.message)) {
throw new Error('Sync stopped: server hrefs do not match the configured WebDAV base URL. Re-link with the correct URL.');
}
throw e;
} Prevention
- Make the configured base URL exactly match the server (scheme, host, port, path).
- Use https consistently if the server redirects http->https.
- Reverse-proxy setups: align the proxy host/port with the configured URL.
- Watch trailing-slash and percent-encoding mismatches.
When it happens
Trigger: list() or stat() returns a d:href that points outside the configured WebDAV base — e.g. the server rewrites hrefs to an absolute URL with a different host/path, returns a redirect target, or the configured base URL doesn't match the actual server URL.
Common situations: Base URL configured with http while server redirects to https (or vice-versa); behind a reverse proxy that rewrites the host/port; Nextcloud/Seafile returning hrefs with a different path prefix than the configured root; trailing-slash mismatch between config and server; CDN/front-end altering URLs.
Related errors
- Invalid WebDAV resource format: ${JSON.stringify(resource)}
- Could not get lastModified date for resource: ${JSON.stringi
- Cannot initialise synchroniser.
- WebDAV directory not found: ${options.path()}
- Invalid date: ${lastModifiedString}
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/07513ca9f009b38e.
Report an issue: GitHub.