theonedev/onedev · error · ExplicitException

Readonly file has been changed during upgrade:

Error message

Readonly file has been changed during upgrade: 

What it means

During restoreProgramFiles in the upgrade handler, files marked read-only in the new program files are copied over existing ones only if the existing file is writable OR byte-identical. If a read-only destination file differs from the source, OneDev assumes local modifications and refuses to overwrite it, throwing this ExplicitException naming the file.

Source

Thrown at server-core/src/main/java/io/onedev/server/commandhandler/Upgrade.java:605

						else 
							FileUtils.deleteDir(destChildFile);
					}
				}
			} else {
				if (destFile.isFile())
					FileUtils.deleteFile(destFile);
				FileUtils.createDir(destFile);
			}
			for (var srcChildFile: srcFile.listFiles())
				restoreProgramFiles(srcChildFile, new File(destFile, srcChildFile.getName()));
		} else try {
			if (destFile.isDirectory())
				FileUtils.deleteDir(destFile);
			if (destFile.exists()) {
				if (destFile.canWrite())
					FileUtils.copyFile(srcFile, destFile);
				else if (!Arrays.equals(FileUtils.readFileToByteArray(srcFile), FileUtils.readFileToByteArray(destFile)))
					throw new ExplicitException("Readonly file has been changed during upgrade: " + destFile.getAbsolutePath());
			} else {
				FileUtils.copyFile(srcFile, destFile);
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}
	
	protected void updateProgramFiles(File upgradeDir, int oldAppDataVersion) {
		cleanAndCopy(new File(Bootstrap.installDir, "3rdparty-licenses"), new File(upgradeDir, "3rdparty-licenses"));

		File siteServerScriptFile = new File(upgradeDir, "bin/server.sh");
		for (File file: Bootstrap.getBinDir().listFiles()) {
			if (!file.getName().endsWith(".pid") && !file.getName().endsWith(".status")) {
				try {
					String serverScript = null;
					if (file.getName().equals("server.sh")) {
						serverScript = FileUtils.readFileToString(file, Charset.defaultCharset());

View on GitHub (pinned to d44925c47c)

Solutions

  1. Revert the named file to its original shipped content (or restore the whole previous program files from backup)
  2. Make the file writable so the upgrade can overwrite it: chmod u+w <file>
  3. Delete the changed file so the upgrade copies the fresh version in its place
  4. Re-run the upgrade command after fixing the file

Example fix

// before
chmod 444 site/conf/system.properties && edit it
// after
chmod u+w site/conf/system.properties && revert edits, then run upgrade
Defensive patterns

Strategy: validation

Validate before calling

// compare readonly files before upgrading
if (file.canWrite() == false && !Arrays.equals(readAll(src), readAll(dest))) revertFile(dest);

Try / catch

try { upgrade(); } catch (ExplicitException e) { if (e.getMessage().startsWith("Readonly file has been changed")) restoreOriginalFile(extractPath(e.getMessage())); }

Prevention

When it happens

Trigger: Running an upgrade where the new version ships a read-only file (e.g. license, config template) whose content differs from the read-only file present in the current installation directory.

Common situations: Admins manually editing supposedly read-only files in the OneDev site directory, partial manual patches, or filesystem sync tools changing file contents between upgrades.

Related errors


AI-assisted analysis of theonedev/onedev@d44925c47c (2026-09-06). Data as JSON: /api/errors/25c2d06d218557d3. Report an issue: GitHub.