laurent22/joplin · error · Error
Could not get lastModified date for resource
Error message
Could not get lastModified date for resource: ${JSON.stringify(resource)} What it means
Thrown by statFromResource_() when a WebDAV file resource has no d:getlastmodified property. The driver tolerates missing dates for directories (some servers like Seafile omit them) but requires one for files, since without it the sync timestamp comparison is impossible.
Solutions
- Switch to a more standards-compliant WebDAV server or enable its full property support (Nextcloud/ownCloud recommended).
- Update the WebDAV server to a version that emits getlastmodified for all files.
- If stuck on a non-compliant server, patch the driver to fall back to the current time and accept weaker timestamp accuracy.
- Verify the resource is actually a file and not a virtual entry exposed by the server.
Example fix
// before
if (!lastModifiedString && !isDir) throw new Error(`Could not get lastModified date for resource: ${JSON.stringify(resource)}`);
// after - warn and degrade gracefully for non-conformant servers
if (!lastModifiedString && !isDir) {
logger.warn('WebDAV server omitted getlastmodified; using current time:', resource);
lastModifiedString = new Date().toString();
} Defensive patterns
Strategy: fallback
Validate before calling
// Pre-validate server compliance by statting a known file via PROPFIND.
const probe = await fetch(`${baseUrl}/.joplin-test`, { method: 'PROPFIND', headers: { Depth: '0', ...auth } });
const body = await probe.text();
if (!body.includes('getlastmodified')) {
throw new Error('WebDAV server does not return getlastmodified; pick another server.');
} Type guard
function resourceHasLastModified(resource: any): boolean {
return !!resource && !!(resource?.['d:getlastmodified'] ?? resource?.['D:getlastmodified']);
} Try / catch
try {
return await driver.stat(path);
} catch (e) {
if (/Could not get lastModified/.test(e.message)) {
logger.warn('Non-compliant WebDAV; degrading timestamp accuracy for', path);
return { path, updated_time: Date.now(), isDir: false };
}
throw e;
} Prevention
- Choose a standards-compliant WebDAV server that emits getlastmodified for files.
- Keep the WebDAV server updated.
- If stuck on a non-compliant server, patch the driver to degrade gracefully.
- Test the server with a PROPFIND probe before relying on it for sync.
When it happens
Trigger: stat() or list() on a WebDAV server that does not return the getlastmodified property for a file element — non-compliant server or a resource type the server treats specially.
Common situations: Seafile or other non-conformant WebDAV implementation; a virtual/special file (e.g. an alias or shortcut) that the server exposes without standard properties; server bug after an update; custom WebDAV gateway (e.g. to S3) that strips properties.
Related errors
- href not in baseUrl nor relativeBaseUrl
- Invalid WebDAV resource format
- Invalid date
- 404
- Cannot initialise synchroniser.
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/3831c1780c5a7e11.
Report an issue: GitHub.
Appendix: source
Thrown at packages/lib/file-api-driver-webdav.js:77
}
}
}
let lastModifiedString = null;
try {
lastModifiedString = this.api().resourcePropByName(resource, 'string', 'd:getlastmodified');
} catch (error) {
if (error.code === 'stringNotFound') {
// OK - the logic to handle this is below
} else {
throw error;
}
}
// Note: Not all WebDAV servers return a getlastmodified date (eg. Seafile, which doesn't return the
// property for folders) so we can only throw an error if it's a file.
if (!lastModifiedString && !isDir) throw new Error(`Could not get lastModified date for resource: ${JSON.stringify(resource)}`);
const lastModifiedDate = lastModifiedString ? new Date(lastModifiedString) : new Date();
if (isNaN(lastModifiedDate.getTime())) throw new Error(`Invalid date: ${lastModifiedString}`);
return {
path: path,
updated_time: lastModifiedDate.getTime(),
isDir: isDir,
};
}
async setTimestamp() {
throw new Error('Not implemented'); // Not needed anymore
}
async delta(path, options) {
const getDirStats = async path => {
const result = await this.list(path, { includeDirs: false });
return result.items;View on GitHub (pinned to 2654b33620)