asLody/VirtualApp · error · java.lang.IllegalArgumentException
Exactly one of stageDir or stageCid stage must be set
Error message
Exactly one of stageDir or stageCid stage must be set
What it means
A session's backing storage must be identified exactly once: either a stageDir (directory on disk) or a stageCid (encrypted-container ID). open() checks on first preparation that stageDir is non-null (stageCid is handled by the caller's context); if stageDir is null it throws IllegalArgumentException('Exactly one of stageDir or stageCid stage must be set').
Solutions
- Pass a valid, writable stage directory (e.g. new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(sessionId))) to open().
- Provide exactly one of stageDir or stageCid — never both null (and avoid both set).
- Confirm the stage dir exists (mkdirs) and is accessible before open().
Example fix
// before session.open(null, userId); // IllegalArgumentException // after File stageDir = new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(session.sessionId)); stageDir.mkdirs(); session.open(stageDir, userId);
Defensive patterns
Strategy: validation
Validate before calling
File stageDir = new File(VEnvironment.getPackageInstallerStageDir(), String.valueOf(sessionId));
if (stageDir == null || (!stageDir.exists() && !stageDir.mkdirs())) {
throw new IOException("Cannot prepare stage dir: " + stageDir);
}
session.open(stageDir, userId); Type guard
boolean hasValidStage(File stageDir) { return stageDir != null && (stageDir.isDirectory() || stageDir.mkdirs()); } Try / catch
try {
session.open(stageDir, userId);
} catch (IllegalArgumentException e) {
if (String.valueOf(e.getMessage()).startsWith("Exactly one of stageDir")) {
session.open(defaultStageDirFor(session.sessionId), userId);
} else throw e;
} Prevention
- Never call open(null, ...); always derive stageDir from the session ID.
- Create the stage directory before opening.
- Keep stageDir/stageCid handling in a single helper.
When it happens
Trigger: Calling session.open(null, userId) on a first-time (unprepared) session; constructing PackageInstallerSession with a null stage dir and then opening it; opening a session twice where the first open never set mPrepared due to a null arg.
Common situations: Custom code paths that build sessions without VEnvironment.getPackageInstallerStageDir()-derived directories; copy-paste from AOSP variants where stageCid was used but VirtualApp expects a stageDir; refactors dropping the stageDir argument.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
AI-assisted analysis of asLody/VirtualApp@666fefcb5d (2026-09-09).
Data as JSON: /api/errors/526d73070dee9455.
Report an issue: GitHub.
Appendix: source
Thrown at VirtualApp/lib/src/main/java/com/lody/virtual/server/pm/installer/PackageInstallerSession.java:481
synchronized (mLock) {
mPermissionsAccepted = true;
}
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
} else {
destroyInternal();
dispatchSessionFinished(INSTALL_FAILED_ABORTED, "User rejected permissions", null);
}
}
public void open() throws IOException {
if (mActiveCount.getAndIncrement() == 0) {
mCallback.onSessionActiveChanged(this, true);
}
synchronized (mLock) {
if (!mPrepared) {
if (stageDir == null) {
throw new IllegalArgumentException(
"Exactly one of stageDir or stageCid stage must be set");
}
mPrepared = true;
mCallback.onSessionPrepared(this);
}
}
}
public static String getCompleteMessage(Throwable t) {
final StringBuilder builder = new StringBuilder();
builder.append(t.getMessage());
while ((t = t.getCause()) != null) {
builder.append(": ").append(t.getMessage());
}
return builder.toString();
}
View on GitHub (pinned to 666fefcb5d)