diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc
index 918ac0f87..5a01c9642 100644
--- a/CHANGELOG.adoc
+++ b/CHANGELOG.adoc
@@ -4,6 +4,8 @@ This file documents all notable changes to https://github.com/devonfw/IDEasy[IDE
== 2026.07.002
+* https://github.com/devonfw/IDEasy/issues/2102[#2102]: Make context.getIdeRoot() consistent during initial install
+
Release with new features and bugfixes:
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
index dac769796..fbd560518 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/AbstractIdeContext.java
@@ -111,7 +111,7 @@ public abstract class AbstractIdeContext implements IdeContext, IdeLogArgFormatt
private Path ideHome;
- private final Path ideRoot;
+ private Path ideRoot;
private Path confPath;
@@ -587,6 +587,12 @@ public Path getIdeRoot() {
return this.ideRoot;
}
+ @Override
+ public void setIdeRoot(Path ideRoot) {
+
+ this.ideRoot = ideRoot;
+ }
+
@Override
public Path getIdePath() {
diff --git a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
index e8279c717..c058b7373 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/context/IdeContext.java
@@ -51,7 +51,7 @@
*
{@link #getGitContext() git context} (for git operations like clone, fetch, pull, etc.)
* {@link #getSystemInfo() system info} (for information about OS and CPU architecture)
* {@link #getVariables() environment variables} (to access and modify IDEasy variables according to our configuration layout)
- *
+ *
* {@link #question(Object[], String, Object...) question} (for interaction to let the end-user decide)
* {@link #getUrls() url metadata} (access ide-urls to find versions, download URLs, dependency and security metadata)
* {@link #getDefaultToolRepository() tool repository} (for abstraction of version resolution and download of tools)
@@ -409,6 +409,13 @@ default void requireOnline(String purpose, boolean explicitOnlineCheck) {
*/
Path getIdeRoot();
+ /**
+ * @param ideRoot the new value of {@link #getIdeRoot() IDE_ROOT}. Typically detected automatically from the environment and working directory, but may need
+ * to be set explicitly (e.g. during the initial installation where the {@code IDE_ROOT} environment variable is not yet available but the install target
+ * is already known).
+ */
+ void setIdeRoot(Path ideRoot);
+
/**
* @return the {@link Path} to the {@link #FOLDER_UNDERSCORE_IDE}.
* @see #getIdeRoot()
diff --git a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java
index a762cd2c2..7f0683425 100644
--- a/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java
+++ b/cli/src/main/java/com/devonfw/tools/ide/tool/IdeasyCommandlet.java
@@ -266,6 +266,9 @@ private boolean isSameSnapshotVersion(String installed, String latest) {
*/
public void installIdeasy(Path cwd) {
Path ideRoot = determineIdeRoot(cwd);
+ // During a fresh (MSI) installation the IDE_ROOT environment variable is not yet available, so context.getIdeRoot() would return null for the whole run.
+ // The install target is already known here, so we make the context consistent for any downstream code reading context.getIdeRoot() (see #1517).
+ this.context.setIdeRoot(ideRoot);
Path idePath = ideRoot.resolve(IdeContext.FOLDER_UNDERSCORE_IDE);
Path installationPath = idePath.resolve(IdeContext.FOLDER_INSTALLATION);
Path ideasySoftwarePath = idePath.resolve(IdeContext.FOLDER_SOFTWARE).resolve(MvnRepository.ID).resolve(IdeasyCommandlet.TOOL_NAME)
diff --git a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
index 84022d2a0..5928b3b55 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/context/AbstractIdeTestContext.java
@@ -55,10 +55,6 @@ public class AbstractIdeTestContext extends AbstractIdeContext {
private TestCommandletManager testCommandletManager;
- private Path ideRoot;
-
- private boolean ideRootSet;
-
private Path urlsPath;
protected final WireMockRuntimeInfo wireMockRuntimeInfo;
@@ -299,24 +295,6 @@ public void setUserHome(Path dummyUserHome) {
super.setUserHome(dummyUserHome);
}
- @Override
- public Path getIdeRoot() {
-
- if (this.ideRootSet) {
- return this.ideRoot;
- }
- return super.getIdeRoot();
- }
-
- /**
- * @param ideRoot the new value of {@link #getIdeRoot()}.
- */
- public void setIdeRoot(Path ideRoot) {
-
- this.ideRoot = ideRoot;
- this.ideRootSet = true;
- }
-
@Override
public Path getUrlsPath() {
diff --git a/cli/src/test/java/com/devonfw/tools/ide/tool/IdeasyCommandletTest.java b/cli/src/test/java/com/devonfw/tools/ide/tool/IdeasyCommandletTest.java
index 945e7fc19..487dcefab 100644
--- a/cli/src/test/java/com/devonfw/tools/ide/tool/IdeasyCommandletTest.java
+++ b/cli/src/test/java/com/devonfw/tools/ide/tool/IdeasyCommandletTest.java
@@ -114,6 +114,35 @@ void testInstallIdeasy(String os) {
+ addedRcLines);
}
+ /**
+ * Test that {@link IdeasyCommandlet#installIdeasy(Path)} updates {@link IdeContext#getIdeRoot()} to the derived install target when {@code IDE_ROOT} is not
+ * set in the environment (as is the case during a fresh MSI installation). This ensures downstream code reading {@link IdeContext#getIdeRoot()} during the
+ * same install run gets a consistent value. See #1517 for reference.
+ *
+ * @param os to use
+ */
+ @ParameterizedTest
+ @ValueSource(strings = { "windows", "mac", "linux" })
+ void testInstallIdeasyUpdatesIdeRoot(String os) {
+
+ // arrange
+ SystemInfo systemInfo = SystemInfoMock.of(os);
+ IdeTestContext context = newContext("install");
+ context.setIdeRoot(null);
+ context.setSystemInfo(systemInfo);
+ context.getStartContext().setForceMode(true);
+ Path gitconfigPath = context.getUserHome().resolve(".gitconfig");
+ FileAccess fileAccess = new FileAccessImpl(context);
+ fileAccess.writeFileContent("", gitconfigPath);
+ Path ideRoot = context.getUserHome().resolve("projects");
+ IdeasyCommandlet ideasy = new IdeasyCommandlet(context);
+ assertThat(context.getIdeRoot()).as("IDE_ROOT is not set before install").isNull();
+ // act
+ ideasy.installIdeasy(context.getUserHome().resolve("Downloads/ide-cli"));
+ // assert
+ assertThat(context.getIdeRoot()).isEqualTo(ideRoot);
+ }
+
/**
* Test of {@link IdeasyCommandlet#configureWindowsTerminalGitBash()}, also when {@code IDE_ROOT} is not yet set, as is the case during MSI installation.
*