diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index 16bc4ae689ce7ac76e7ecf0a07eb5c9f562dcaa8..298fd48f869f4e3f83abe76df38508cb1f120b1c 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -23,6 +23,12 @@ struct App
     // FIXME: add args, sandbox settings, metadata, ...
 };
 
+struct UnresolvedApp
+{
+    App unresolved;
+    App resolve(ref<Store>);
+};
+
 struct Installable
 {
     virtual ~Installable() { }
@@ -33,7 +39,7 @@ struct Installable
 
     DerivedPath toDerivedPath();
 
-    App toApp(EvalState & state);
+    UnresolvedApp toApp(EvalState & state);
 
     virtual std::pair<Value *, Pos> toValue(EvalState & state)
     {
diff --git a/src/nix/app.cc b/src/nix/app.cc
index 1e30deb346bd67d732d1e990adc4f5fe466a27dc..bdc64a886ebc05c6de88ed1848c5aa98d7c4c7c3 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -58,33 +58,24 @@ std::string resolveString(Store & store, const std::string & toResolve, const Bu
     return rewriteStrings(toResolve, rewrites);
 }
 
-App Installable::toApp(EvalState & state)
+UnresolvedApp Installable::toApp(EvalState & state)
 {
     auto [cursor, attrPath] = getCursor(state);
 
     auto type = cursor->getAttr("type")->getString();
 
-    auto checkProgram = [&](const Path & program)
-    {
-        if (!state.store->isInStore(program))
-            throw Error("app program '%s' is not in the Nix store", program);
-    };
+    if (type == "app") {
+        auto [program, context] = cursor->getAttr("program")->getStringWithContext();
 
-    std::vector<std::shared_ptr<Installable>> context;
-    std::string unresolvedProgram;
 
+        std::vector<StorePathWithOutputs> context2;
+        for (auto & [path, name] : context)
+            context2.push_back({state.store->parseStorePath(path), {name}});
 
-    if (type == "app") {
-        auto [program, context_] = cursor->getAttr("program")->getStringWithContext();
-        unresolvedProgram = program;
-
-        for (auto & [path, name] : context_)
-            context.push_back(std::make_shared<InstallableDerivedPath>(
-                state.store,
-                DerivedPathBuilt{
-                    .drvPath = state.store->parseStorePath(path),
-                    .outputs = {name},
-                }));
+        return UnresolvedApp{App {
+            .context = std::move(context2),
+            .program = program,
+        }};
     }
 
     else if (type == "derivation") {
@@ -98,24 +89,33 @@ App Installable::toApp(EvalState & state)
             aMainProgram
             ? aMainProgram->getString()
             : DrvName(name).name;
-        unresolvedProgram = outPath + "/bin/" + mainProgram;
-        context = {std::make_shared<InstallableDerivedPath>(
-            state.store,
-            DerivedPathBuilt{
-                .drvPath = drvPath,
-                .outputs = {outputName},
-            })};
+        auto program = outPath + "/bin/" + mainProgram;
+        return UnresolvedApp { App {
+            .context = { { drvPath, {outputName} } },
+            .program = program,
+        }};
     }
 
     else
         throw Error("attribute '%s' has unsupported type '%s'", attrPath, type);
+}
+
+App UnresolvedApp::resolve(ref<Store> store)
+{
+    auto res = unresolved;
+
+    std::vector<std::shared_ptr<Installable>> installableContext;
+
+    for (auto & ctxElt : unresolved.context)
+        installableContext.push_back(
+            std::make_shared<InstallableDerivedPath>(store, ctxElt.toDerivedPath()));
 
-    auto builtContext = build(state.store, Realise::Outputs, context);
-    auto program = resolveString(*state.store, unresolvedProgram, builtContext);
-    checkProgram(program);
-    return App {
-        .program = program,
-    };
+    auto builtContext = build(store, Realise::Outputs, installableContext);
+    res.program = resolveString(*store, unresolved.program, builtContext);
+    if (store->isInStore(res.program))
+        throw Error("app program '%s' is not in the Nix store", res.program);
+
+    return res;
 }
 
 }
diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc
index 53dccc63af65480313a4424cad5c3137d3c26143..88bc3d1d1c8c2923b099ad79c34873e79912e7f5 100644
--- a/src/nix/bundle.cc
+++ b/src/nix/bundle.cc
@@ -69,8 +69,7 @@ struct CmdBundle : InstallableCommand
     {
         auto evalState = getEvalState();
 
-        auto app = installable->toApp(*evalState);
-        store->buildPaths(toDerivedPaths(app.context));
+        auto app = installable->toApp(*evalState).resolve(store);
 
         auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath("."));
         const flake::LockFlags lockFlags{ .writeLockFile = false };
diff --git a/src/nix/run.cc b/src/nix/run.cc
index b5d8ab38a3f4d06dd3d3ede5d642369e97dac31f..f684c5ea479ec1a7bb7a383c5bfcfcbfa569205d 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -178,9 +178,7 @@ struct CmdRun : InstallableCommand, RunCommon
     {
         auto state = getEvalState();
 
-        auto app = installable->toApp(*state);
-
-        state->store->buildPaths(toDerivedPaths(app.context));
+        auto app = installable->toApp(*state).resolve(store);
 
         Strings allArgs{app.program};
         for (auto & i : args) allArgs.push_back(i);