apereo/cas · warning
Failed to delete service definition file
Error message
Failed to delete service definition file [{}] What it means
AbstractResourceBasedServiceRegistry.delete attempts to remove the on-disk file backing a registered service. If File.delete() fails (file missing write permission on the directory, locked by another process, or a race), CAS logs this warning and returns false, leaving the service definition file in place.
Solutions
- Check filesystem permissions on the services directory and file: the CAS process user needs write permission on both.
- Verify the volume is not mounted read-only (docker mount flags, k8s readOnlyRootFilesystem).
- Check for file locks or a process holding the file open, then retry the delete.
- Manually remove the stale file and reload the registry.
Example fix
// before: delete fails silently with warning chown -R cas:cas /etc/cas/config/services // after chmod -R u+rw /etc/cas/config/services # run as the CAS process user
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check before delete File f = new File(filePath); if (f.exists() && !f.canWrite()) throw new AccessDeniedException(filePath); File dir = f.getParentFile(); if (!dir.canWrite()) throw new AccessDeniedException(dir.getPath());
Try / catch
// Treat false return as retryable
boolean deleted = serviceRegistry.delete(service);
if (!deleted) { /* check permissions/locks, retry with backoff or delete manually */ } Prevention
- Run the CAS process as the owner of the services directory.
- Avoid read-only mounts for directories that must accept writes.
- Don't hold service files open in editors/other processes during deletes.
When it happens
Trigger: Calling serviceRegistry.delete(service) (via ServicesManager or the management app) where filename.exists() but filename.delete() returns false; the publishEvent/removeRegisteredService path is skipped only for the warn log but result remains false.
Common situations: Services directory owned by a different user than the CAS process; read-only mounted volume; NFS/Windows file locks; file recreated by a watcher between existence check and delete.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
- [ ] is not readable. Check file permissions
- Unable to create folder
- Configuration file [ ] is not readable/writable or is not a…
- The service definition file could not be saved at
- subordinate directory
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/424ae1e60ab41d5f.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core-services-registry/src/main/java/org/apereo/cas/services/resource/AbstractResourceBasedServiceRegistry.java:218
LOGGER.debug("Saved service to [{}]", fileName.getCanonicalPath());
} catch (final IOException e) {
throw new IllegalArgumentException("IO error opening file stream.", e);
}
return findServiceById(service.getId());
}
@Override
public boolean delete(final RegisteredService service) {
return lock.tryLock(() -> FunctionUtils.doUnchecked(() -> {
val filename = getRegisteredServiceFileName(service);
val clientInfo = ClientInfoHolder.getClientInfo();
publishEvent(new CasRegisteredServicePreDeleteEvent(this, service, clientInfo));
val result = !filename.exists() || filename.delete();
if (result) {
removeRegisteredService(service);
LOGGER.debug("Successfully deleted service definition file [{}]", filename.getCanonicalPath());
} else {
LOGGER.warn("Failed to delete service definition file [{}]", filename.getCanonicalPath());
}
publishEvent(new CasRegisteredServiceDeletedEvent(this, service, clientInfo));
return result;
}));
}
@Override
public void deleteAll() {
val files = FileUtils.listFiles(this.serviceRegistryDirectory.toFile(), getExtensions(), true);
files.forEach(File::delete);
}
@Override
public Collection<RegisteredService> load() {
return lock.tryLock(() -> {
LOGGER.trace("Loading files from [{}]", this.serviceRegistryDirectory);
val serviceRegistryDirectoryFile = serviceRegistryDirectory.toFile();
val files = serviceRegistryDirectoryFile.exists()View on GitHub (pinned to e7288fc434)