Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/participation/cloning.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import * as path from "path";
import { retrieveVcsAccessToken } from "../artemis/authentication.client";
import { getWorkspaceFolder, theiaEnv } from "../theia/theia";
import { addVcsTokenToUrl } from "@shared/models/participation.model";
import { ProgrammingExercise, ProgrammingLanguage } from "@shared/models/exercise.model";
import { warmupGradleDaemon } from "./gradle.service";

export async function cloneUserRepo(repoUrl: string, username: string) {
// get folder to clone repo into
Expand Down Expand Up @@ -48,6 +50,12 @@ export async function cloneUserRepo(repoUrl: string, username: string) {
const cloneUrlWithToken = new URL(addVcsTokenToUrl(repoUrl, username, vcsToken));
const clonePath = await cloneByGivenURL(cloneUrlWithToken, destinationPath);

// Pre-warm Gradle daemon in background to avoid ~3s init on first build
const exercise = getState().displayedExercise;
if ((exercise as ProgrammingExercise)?.programmingLanguage === ProgrammingLanguage.JAVA) {
warmupGradleDaemon(clonePath);
}

if (!theiaEnv.THEIA_FLAG) {
// Prompt the user to open the cloned folder in a new workspace
const openIn = await vscode.window.showInformationMessage(
Expand Down
35 changes: 35 additions & 0 deletions src/participation/gradle.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { spawn } from "child_process";
import * as fs from "fs";
import * as path from "path";

/**
* Pre-warms the Gradle daemon in the background after cloning a repository.
* This avoids the ~3s daemon initialization overhead on the first build.
*
* Silently skips if:
* - Running on Windows (not a supported environment)
* - The project does not contain a `gradlew` file (not a Gradle project)
*/
export function warmupGradleDaemon(projectPath: string): void {
if (process.platform === "win32") {
return;
}

const gradlewPath = path.join(projectPath, "gradlew");
if (!fs.existsSync(gradlewPath)) {
return;
}

try {
fs.chmodSync(gradlewPath, 0o755);

const child = spawn("./gradlew", ["--daemon"], {
cwd: projectPath,
detached: true,
stdio: "ignore",
});
child.unref();
Comment on lines +23 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't auto-execute repository-controlled gradlew during clone.

Because src/participation/cloning.service.ts:53-57 invokes this immediately after cloning, this executes code from the freshly cloned repository before the user has opened or trusted it. gradlew and the wrapper configuration are part of the repo, so a compromised template or participation repository turns this into unintended code execution. Gate this behind explicit opt-in/trust, or defer warmup until the first user-initiated Gradle command.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/participation/gradle.service.ts` around lines 23 - 31, The code currently
changes permissions and auto-spawns the repository-controlled wrapper
(gradlewPath, chmodSync and spawn("./gradlew", ["--daemon"], ...),
child.unref()) which executes code from the cloned repo; change this so the
Gradle warmup is not auto-run during cloning: remove or disable the spawn/unref
call in src/participation/gradle.service.ts and instead gate warmup behind an
explicit trust/opt-in or defer it until the first user-initiated Gradle command
(e.g., add a check like isRepositoryTrusted()/userOptIn flag before calling
spawn or expose a separate warmup method invoked only by the user-triggered
Gradle flow), and ensure cloning.service no longer invokes the warmup
automatically.

Comment on lines +26 to +31
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/participation/gradle.service.ts | head -50

Repository: EduIDE/scorpio

Length of output: 1234


Handle detached spawn failures on the child "error" event.

try/catch only covers the synchronous setup here. If the wrapper cannot be launched at all, Node reports that via child.on("error"), which currently goes unhandled.

Suggested fix
     const child = spawn("./gradlew", ["--daemon"], {
       cwd: projectPath,
       detached: true,
       stdio: "ignore",
     });
+    child.on("error", (error) => {
+      console.warn(`Gradle daemon warmup failed: ${error.message}`);
+    });
     child.unref();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const child = spawn("./gradlew", ["--daemon"], {
cwd: projectPath,
detached: true,
stdio: "ignore",
});
child.unref();
const child = spawn("./gradlew", ["--daemon"], {
cwd: projectPath,
detached: true,
stdio: "ignore",
});
child.on("error", (error) => {
console.warn(`Gradle daemon warmup failed: ${error.message}`);
});
child.unref();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/participation/gradle.service.ts` around lines 26 - 31, Attach an "error"
handler to the spawned child process to catch synchronous spawn failures that
aren’t covered by the surrounding try/catch: after creating const child =
spawn("./gradlew", ["--daemon"], { ... }) (in gradle.service.ts) add
child.on("error", (err) => { /* handle failure */ }) before calling
child.unref(); inside the handler log the error (e.g., using the existing
logger), perform any necessary cleanup, and propagate the failure (throw or
reject the surrounding Promise) so callers know the wrapper failed to start.

} catch (error: any) {
console.warn(`Gradle daemon warmup failed: ${error.message}`);
}
}