Repository: apollographql/rover (language: rust)

Issue:
rover supergraph compose failed if any -o specified
## Description

rover command failed with
```
merging supergraph schema files
supergraph config loaded successfully
error: Failed to read the file at: /tmp/test.
No such file or directory (os error 2)
```
if any `-o` value given for rover v0.27.0. rover v0.26.2 has no issue.

### Steps to reproduce

* install rover 0.27.0
* compose supergraph: `rover supergraph compose --config ${SUBGRAPH_CONFIG_FILE} -o /any/path/here`

### Expected result

Supergraph composed to the `/any/path/here` file

### Actual result

```
merging supergraph schema files
supergraph config loaded successfully
error: Failed to read the file at: /any/path/here.
No such file or directory (os error 2)
```

### Environment

```
# rover info
Rover Info:
Version: 0.27.0
Install Location: /root/.rover/bin/rover
OS: Debian 12.0.0 [64-bit]
Shell: Unknown
```




Discussion / hints:
We are impacted by this as well, command seems to work locally on windows, but fails in a deployed docker container
Thanks for submitting this report. This should be fixed in our next patch release.
@dotdat correct me if I'm wrong, solution you created should take care about directory structure in case path to output file does not exist. 
If that's true and the only one logical change present in the commit, it will not fix original issue, unfortunately. In my case, all path components exists, process have perms to change all path components, (e.g. rwx for directory), but error still present. 

PS.
Thanks for fast reaction!
@dfndr-1 You are correct in what the solution does. I was perhaps a bit too hasty on the reproduction case and found that this error could be reproduced if the path to the output file didn't exist.

It also seems that github was a bit too quick on closing the issue after I merged that change, so I'll re-open this as we try to find a better way of reproducing the issue here.
@dfndr-1 Apologies for the double ping, but would you mind providing additional logs by running the command with `-l debug` as such:

`rover -l debug supergraph compose --config ${SUBGRAPH_CONFIG_FILE} -o /any/path/here`
@dotdat, Sure. 

I included relevant logs, let me know if you need full log (have to obscure it a bit)

```
root@9d34f58a434f:/# rover -l debug supergraph compose --config ${SUBGRAPH_CONFIG_FILE} -o /tmp/file.graphql
...
   INFO rover_std::fs: checking existence of parent path in '/tmp/.tmpSySY21/supergraph.yaml'
    at crates/rover-std/src/fs.rs:59

  DEBUG rover_std::fs: attempting to canonicalize parent path '/tmp/.tmpSySY21'
    at crates/rover-std/src/fs.rs:100

  DEBUG rover_std::fs: could not canonicalize parent path '/tmp/.tmpSySY21', attempting to create interim paths
    at crates/rover-std/src/fs.rs:104
  
  INFO rover_std::fs: creating /tmp/.tmpSySY21 directory
    at crates/rover-std/src/fs.rs:130

  DEBUG rover_std::fs: interim paths created for /tmp/.tmpSySY21
    at crates/rover-std/src/fs.rs:110

  DEBUG rover_std::fs: final canonical path is /tmp/.tmpSySY21/supergraph.yaml
    at crates/rover-std/src/fs.rs:77

   INFO rover_std::fs: writing /tmp/.tmpSySY21/supergraph.yaml to disk
    at crates/rover-std/src/fs.rs:90

error: Failed to read the file at: /tmp/file.graphql.
No such file or directory (os error 2)

root@9d34f58a434f:/# ls -lh /tmp/.tmpSySY21/
-rw-r--r-- 1 root root 138K Feb 12 22:59 supergraph.yaml
```
@dfndr-1 Thanks for providing the logs. Would you also mind sharing which federation version you're targeting? This could potentially be related to the supergraph plugin and this will help reproduce that more accurately.

Fix patch:
diff --git a/src/command/dev/do_dev.rs b/src/command/dev/do_dev.rs
index 2e5595a9f..3a7defadd 100644
--- a/src/command/dev/do_dev.rs
+++ b/src/command/dev/do_dev.rs
@@ -21,7 +21,6 @@ use crate::command::dev::{OVERRIDE_DEV_COMPOSITION_VERSION, OVERRIDE_DEV_ROUTER_
 use crate::command::Dev;
 use crate::composition::events::CompositionEvent;
 use crate::composition::pipeline::CompositionPipeline;
-use crate::composition::supergraph::binary::OutputTarget;
 use crate::composition::supergraph::config::full::introspect::MakeResolveIntrospectSubgraph;
 use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::MakeFetchRemoteSubgraph;
 use crate::composition::supergraph::config::resolver::fetch_remote_subgraphs::MakeFetchRemoteSubgraphs;
@@ -183,13 +182,11 @@ impl Dev {
         let composition_runner = composition_pipeline
             .runner(
                 exec_command_impl,
-                read_file_impl.clone(),
                 write_file_impl.clone(),
                 client_config.service()?,
                 fetch_remote_subgraph_factory.boxed_clone(),
                 self.opts.subgraph_opts.subgraph_polling_interval,
                 tmp_config_dir_path.clone(),
-                OutputTarget::Stdout,
                 true,
                 federation_updater_config,
             )
diff --git a/src/command/lsp/mod.rs b/src/command/lsp/mod.rs
index 866d18347..c1bfa9d61 100644
--- a/src/command/lsp/mod.rs
+++ b/src/command/lsp/mod.rs
@@ -22,7 +22,6 @@ use crate::command::lsp::errors::StartCompositionError::SupergraphYamlUrlConvers
 use crate::composition::events::CompositionEvent;
 use crate::composition::pipeline::CompositionPipeline;
 use crate::composition::runner::CompositionRunner;
-use crate::composition::supergraph::binary::OutputTarget;
 use crate::composition::supergraph::config::error::ResolveSubgraphError;
 use crate::composition::supergraph::config::full::introspect::MakeResolveIntrospectSubgraph;
 use crate::composition::supergraph::config::resolver::fetch_remote_subgraph::MakeFetchRemoteSubgraph;
@@ -36,7 +35,6 @@ use crate::composition::{
 use crate::options::PluginOpts;
 use crate::utils::client::StudioClientConfig;
 use crate::utils::effect::exec::TokioCommand;
-use crate::utils::effect::read_file::FsReadFile;
 use crate::utils::effect::write_file::FsWriteFile;
 use crate::utils::parsers::FileDescriptorType;
 use crate::{RoverOutput, RoverResult};
@@ -161,7 +159,7 @@ async fn run_lsp(client_config: StudioClientConfig, lsp_opts: LspOpts) -> RoverR
 }
 
 async fn start_composition(
-    runner: CompositionRunner<TokioCommand, FsReadFile, FsWriteFile>,
+    runner: CompositionRunner<TokioCommand, FsWriteFile>,
     language_server: ApolloLanguageServer,
     supergraph_yaml_url: Url,
 ) -> Result<(), StartCompositionError> {
@@ -320,7 +318,7 @@ async fn create_composition_runner(
     federation_version: Option<FederationVersion>,
     client_config: StudioClientConfig,
     lsp_opts: LspOpts,
-) -> Result<CompositionRunner<TokioCommand, FsReadFile, FsWriteFile>, StartCompositionError> {
+) -> Result<CompositionRunner<TokioCommand, FsWriteFile>, StartCompositionError> {
     let fetch_remote_subgraphs_factory = MakeFetchRemoteSubgraphs::builder()
         .studio_client_config(client_config.clone())
         .profile(lsp_opts.plugin_opts.profile.clone())
@@ -360,13 +358,11 @@ async fn create_composition_runner(
     Ok(composition_pipeline
         .runner(
             TokioCommand::default(),
-            FsReadFile::default(),
             FsWriteFile::default(),
             client_config.service()?,
             fetch_remote_subgraph_factory.boxed_clone(),
             lsp_opts.introspection_polling_interval,
             Utf8PathBuf::try_from(temp_dir())?,
-            OutputTarget::InMemory,
             true,
             Some(FederationUpdaterConfig {
                 studio_client_config: client_config,
diff --git a/src/command/supergraph/compose/do_compose.rs b/src/command/supergraph/compose/do_compose.rs
index f2e084629..4247d902e 100644
--- a/src/command/supergraph/compose/do_compose.rs
+++ b/src/command/supergraph/compose/do_compose.rs
@@ -1,4 +1,4 @@
-use std::io::stdin;
+use std::{fs, io::stdin};
 
 use apollo_federation_types::config::FederationVersion;
 use camino::Utf8PathBuf;
@@ -11,8 +11,7 @@ use tower::ServiceExt;
 use crate::options::PluginOpts;
 use crate::utils::client::StudioClientConfig;
 use crate::utils::effect::exec::TokioCommand;
-use crate::utils::effect::read_file::FsReadFile;
-use crate::utils::effect::write_file::FsWriteFile;
+use crate::utils::effect::write_file::{FsWriteFile, WriteFile};
 use crate::utils::parsers::FileDescriptorType;
 use crate::{RoverOutput, RoverResult};
 
@@ -74,7 +73,6 @@ impl Compose {
             },
         };
 
-        let read_file_impl = FsReadFile::default();
         let write_file_impl = FsWriteFile::default();
         let exec_command_impl = TokioCommand::default();
         let supergraph_yaml = self
@@ -123,14 +121,21 @@ impl Compose {
             )
             .await?;
         let composition_success = composition_pipeline
-            .compose(
-                &exec_command_impl,
-                &read_file_impl,
-                &write_file_impl,
-                output_file,
-            )
+            .compose(&exec_command_impl, &write_file_impl)
             .await?;
 
+        if let Some(output_file) = output_file {
+            let parent = output_file.parent();
+            if let Some(parent) = parent {
+                if !parent.exists() {
+                    fs::create_dir_all(parent)?;
+                }
+            }
+            write_file_impl
+                .write_file(&output_file, composition_success.supergraph_sdl.as_bytes())
+                .await?;
+        }
+
         Ok(RoverOutput::CompositionResult(composition_success.into()))
     }
 }
diff --git a/src/composition/mod.rs b/src/composition/mod.rs
index be56b9da4..f892e7c11 100644
--- a/src/composition/mod.rs
+++ b/src/composition/mod.rs
@@ -49,9 +49,9 @@ pub enum CompositionError {
         stdout: String,
         stderr: String,
     },
-    #[error("Failed to parse output of `{binary} compose`")]
+    #[error("Failed to parse output of `{binary} compose`\n{error}")]
     InvalidOutput { binary: Utf8PathBuf, error: String },
-    #[error("Invalid input for `{binary} compose`")]
+    #[error("Invalid input for `{binary} compose`\n{error}")]
     InvalidInput { binary: Utf8PathBuf, error: String },
     #[error("Failed to read the file at: {path}.\n{error}")]
     ReadFile {
diff --git a/src/composition/pipeline.rs b/src/composition/pipeline.rs
index eb4133c7d..e8f63fe68 100644
--- a/src/composition/pipeline.rs
+++ b/src/composition/pipeline.rs
@@ -14,7 +14,6 @@ use tower::MakeService;
 use tracing::{debug, warn};
 
 use super::runner::{CompositionRunner, Runner};
-use super::supergraph::binary::OutputTarget;
 use super::supergraph::config::error::ResolveSubgraphError;
 use super::supergraph::config::full::introspect::ResolveIntrospectSubgraphFactory;
 use super::supergraph::config::resolver::fetch_remote_subgraph::FetchRemoteSubgraphFactory;
@@ -31,7 +30,6 @@ use crate::options::LicenseAccepter;
 use crate::utils::client::StudioClientConfig;
 use crate::utils::effect::exec::ExecCommand;
 use crate::utils::effect::install::InstallBinary;
-use crate::utils::effect::read_file::ReadFile;
 use crate::utils::effect::read_stdin::ReadStdin;
 use crate::utils::effect::write_file::WriteFile;
 use crate::utils::parsers::FileDescriptorType;
@@ -213,9 +211,7 @@ impl CompositionPipeline<state::Run> {
     pub async fn compose(
         &self,
         exec_command_impl: &impl ExecCommand,
-        read_file_impl: &impl ReadFile,
         write_file_impl: &impl WriteFile,
-        output_file: Option<Utf8PathBuf>,
     ) -> Result<CompositionSuccess, CompositionError> {
         let supergraph_config_filepath =
             Utf8PathBuf::from_path_buf(tempdir()?.path().join("supergraph.yaml"))
@@ -252,34 +248,24 @@ impl CompositionPipeline<state::Run> {
         self.state
             .supergraph_binary
             .clone()?
-            .compose(
-                exec_command_impl,
-                read_file_impl,
-                &output_file
-                    .map(OutputTarget::File)
-                    .unwrap_or(OutputTarget::Stdout),
-                supergraph_config_filepath,
-            )
+            .compose(exec_command_impl, supergraph_config_filepath)
             .await
     }
 
     #[tracing::instrument(skip_all)]
     #[allow(clippy::too_many_arguments)]
-    pub async fn runner<ExecC, ReadF, WriteF>(
+    pub async fn runner<ExecC, WriteF>(
         &self,
         exec_command: ExecC,
-        read_file: ReadF,
         write_file: WriteF,
         http_service: HttpService,
         make_fetch_remote_subgraph: FetchRemoteSubgraphFactory,
         introspection_polling_interval: u64,
         output_dir: Utf8PathBuf,
-        output_target: OutputTarget,
         compose_on_initialisation: bool,
         federation_updater_config: Option<FederationUpdaterConfig>,
-    ) -> Result<CompositionRunner<ExecC, ReadF, WriteF>, CompositionPipelineError>
+    ) -> Result<CompositionRunner<ExecC, WriteF>, CompositionPipelineError>
     where
-        ReadF: ReadFile + Debug + Eq + PartialEq + Send + Sync + 'static,
         ExecC: ExecCommand + Debug + Eq + PartialEq + Send + Sync + 'static,
         WriteF: WriteFile + Debug + Eq + PartialEq + Send + Sync + 'static,
     {
@@ -320,11 +306,9 @@ impl CompositionPipeline<state::Run> {
                 resolution_errors,
                 self.state.supergraph_binary.clone(),
                 exec_command,
-                read_file,
                 write_file,
                 output_dir,
                 compose_on_initialisation,
-                output_target,
                 federation_updater_config,
             );
         Ok(runner)
diff --git a/src/composition/runner/mod.rs b/src/composition/runner/mod.rs
index 05e254a20..ce47afb3e 100644
--- a/src/composition/runner/mod.rs
+++ b/src/composition/runner/mod.rs
@@ -22,7 +22,6 @@ use super::supergraph::config::resolver::fetch_remote_subgraph::FetchRemoteSubgr
 use super::watchers::composition::CompositionWatcher;
 use super::watchers::subgraphs::SubgraphWatchers;
 use super::FederationUpdaterConfig;
-use crate::composition::supergraph::binary::OutputTarget;
 use crate::composition::supergraph::config::full::introspect::ResolveIntrospectSubgraphFactory;
 use crate::composition::supergraph::install::InstallSupergraphError;
 use crate::composition::watchers::federation::FederationWatcher;
@@ -30,7 +29,6 @@ use crate::composition::watchers::watcher::file::FileWatcher;
 use crate::composition::watchers::watcher::supergraph_config::SupergraphConfigWatcher;
 use crate::subtask::{BroadcastSubtask, Subtask, SubtaskRunStream, SubtaskRunUnit};
 use crate::utils::effect::exec::ExecCommand;
-use crate::utils::effect::read_file::ReadFile;
 use crate::utils::effect::write_file::WriteFile;
 
 mod state;
@@ -130,22 +128,19 @@ impl Runner<state::SetupSupergraphConfigWatcher> {
 impl Runner<state::SetupCompositionWatcher> {
     /// Configures the composition watcher
     #[allow(clippy::too_many_arguments)]
-    pub fn setup_composition_watcher<ExecC, ReadF, WriteF>(
+    pub fn setup_composition_watcher<ExecC, WriteF>(
         self,
         initial_supergraph_config: FullyResolvedSupergraphConfig,
         initial_resolution_errors: BTreeMap<String, ResolveSubgraphError>,
         supergraph_binary: Result<SupergraphBinary, InstallSupergraphError>,
         exec_command: ExecC,
-        read_file: ReadF,
         write_file: WriteF,
         temp_dir: Utf8PathBuf,
         compose_on_initialisation: bool,
-        output_target: OutputTarget,
         federation_updater_config: Option<FederationUpdaterConfig>,
-    ) -> Runner<state::Run<ExecC, ReadF, WriteF>>
+    ) -> Runner<state::Run<ExecC, WriteF>>
     where
         ExecC: ExecCommand + Debug + Eq + PartialEq + Send + Sync + 'static,
-        ReadF: ReadFile + Debug + Eq + PartialEq + Send + Sync + 'static,
         WriteF: WriteFile + Debug + Eq + PartialEq + Send + Sync + 'static,
     {
         // Create a handler for supergraph composition events.
@@ -154,11 +149,9 @@ impl Runner<state::SetupCompositionWatcher> {
             .initial_resolution_errors(initial_resolution_errors)
             .supergraph_binary(supergraph_binary)
             .exec_command(exec_command)
-            .read_file(read_file)
             .write_file(write_file)
             .temp_dir(temp_dir)
-            .compose_on_initialisation(compose_on_initialisation)
-            .output_target(output_target);
+            .compose_on_initialisation(compose_on_initialisation);
 
         let composition_watcher = if let Some(federation_updater_config) = federation_updater_config
         {
@@ -181,12 +174,11 @@ impl Runner<state::SetupCompositionWatcher> {
 }
 
 /// Alias for a [`Runner`] that is ready to be run
-pub type CompositionRunner<ExecC, ReadF, WriteF> = Runner<state::Run<ExecC, ReadF, WriteF>>;
+pub type CompositionRunner<ExecC, WriteF> = Runner<state::Run<ExecC, WriteF>>;
 
-impl<ExecC, ReadF, WriteF> Runner<state::Run<ExecC, ReadF, WriteF>>
+impl<ExecC, WriteF> Runner<state::Run<ExecC, WriteF>>
 where
     ExecC: ExecCommand + Debug + Eq + PartialEq + Send + Sync + 'static,
-    ReadF: ReadFile + Debug + Eq + PartialEq + Send + Sync + 'static,
     WriteF: WriteFile + Debug + Eq + PartialEq + Send + Sync + 'static,
 {
     /// Runs the [`Runner`]
diff --git a/src/composition/runner/state.rs b/src/composition/runner/state.rs
index 3b11c29c8..798e848ab 100644
--- a/src/composition/runner/state.rs
+++ b/src/composition/runner/state.rs
@@ -18,14 +18,13 @@ pub struct SetupCompositionWatcher {
     pub initial_supergraph_config: LazilyResolvedSupergraphConfig,
 }
 
-pub struct Run<ReadF, ExecC, WriteF>
+pub struct Run<ExecC, WriteF>
 where
-    ReadF: Eq + PartialEq + Debug,
     ExecC: Eq + PartialEq + Debug,
     WriteF: Eq + PartialEq + Debug,
 {
     pub supergraph_config_watcher: Option<SupergraphConfigWatcher>,
     pub subgraph_watchers: SubgraphWatchers,
-    pub composition_watcher: CompositionWatcher<ReadF, ExecC, WriteF>,
+    pub composition_watcher: CompositionWatcher<ExecC, WriteF>,
     pub initial_supergraph_config: LazilyResolvedSupergraphConfig,
 }
diff --git a/src/composition/supergraph/binary.rs b/src/composition/supergraph/binary.rs
index fab59b11a..b3055ad44 100644
--- a/src/composition/supergraph/binary.rs
+++ b/src/composition/supergraph/binary.rs
@@ -1,5 +1,4 @@
 use std::fmt::Debug;
-use std::fs;
 use std::process::Stdio;
 
 use apollo_federation_types::{
@@ -8,57 +7,15 @@ use apollo_federation_types::{
 };
 use buildstructor::Builder;
 use camino::Utf8PathBuf;
-use rover_std::warnln;
 use tap::TapFallible;
 
 use super::version::SupergraphVersion;
 use crate::utils::effect::exec::ExecCommandOutput;
 use crate::{
     composition::{CompositionError, CompositionSuccess},
-    utils::effect::{
-        exec::{ExecCommand, ExecCommandConfig},
-        read_file::ReadFile,
-    },
+    utils::effect::exec::{ExecCommand, ExecCommandConfig},
 };
 
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub enum OutputTarget {
-    /// Output to a file, given by the parameter
-    File(Utf8PathBuf),
-    /// Output to stdout (inappropriate for LSP usage because stdout is reserved for the LSP itself)
-    Stdout,
-    /// Produce an output that only exists in memory, for passing to events
-    InMemory,
-}
-
-impl OutputTarget {
-    pub fn align_to_version(self, version: &SupergraphVersion) -> OutputTarget {
-        match self {
-            OutputTarget::File(path) => {
-                if version.supports_output_flag() {
-                    OutputTarget::File(path)
-                } else {
-                    warnln!("ignoring `--output` because it is not supported in this version of the dependent binary, `supergraph`: {}. Upgrade to Federation 2.9.0 or greater to install a version of the binary that supports it.", version);
-                    OutputTarget::Stdout
-                }
-            }
-            OutputTarget::Stdout => OutputTarget::Stdout,
-            OutputTarget::InMemory => OutputTarget::InMemory,
-        }
-    }
-}
-
-/// Make an optional Utf8PathBuf into an OutputTarget; if we have some path, use it as a file; if
-/// we have no path, we use stdout
-impl From<Option<Utf8PathBuf>> for OutputTarget {
-    fn from(value: Option<Utf8PathBuf>) -> Self {
-        match value {
-            Some(file_path) => OutputTarget::File(file_path),
-            None => OutputTarget::Stdout,
-        }
-    }
-}
-
 impl From<std::io::Error> for CompositionError {
     fn from(error: std::io::Error) -> Self {
         CompositionError::Binary {
@@ -74,52 +31,18 @@ pub struct SupergraphBinary {
 }
 
 impl SupergraphBinary {
-    fn prepare_compose_args(
-        &self,
-        output_target: &OutputTarget,
-        supergraph_config_path: &Utf8PathBuf,
-    ) -> Vec<String> {
-        let mut args = vec!["compose".to_string(), supergraph_config_path.to_string()];
-
-        if let OutputTarget::File(output_path) = output_target {
-            args.push(output_path.to_string());
-        }
-
-        args
-    }
-
     pub async fn compose(
         &self,
         exec_impl: &impl ExecCommand,
-        read_file_impl: &impl ReadFile,
-        output_target: &OutputTarget,
         supergraph_config_path: Utf8PathBuf,
     ) -> Result<CompositionSuccess, CompositionError> {
-        let args = self.prepare_compose_args(output_target, &supergraph_config_path);
+        let args = vec!["compose".to_string(), supergraph_config_path.to_string()];
 
-        if let OutputTarget::File(path) = output_target {
-            let parent = path.parent();
-            if let Some(parent) = parent {
-                if !parent.exists() {
-                    fs::create_dir_all(parent).map_err(|err| CompositionError::UpsertFile {
-                        path: path.clone(),
-                        error: Box::new(err),
-                    })?;
-                }
-            }
-        }
-
-        let config = match output_target {
-            OutputTarget::File(_) | OutputTarget::Stdout => ExecCommandConfig::builder()
-                .exe(self.exe.clone())
-                .args(args)
-                .build(),
-            OutputTarget::InMemory => ExecCommandConfig::builder()
-                .exe(self.exe.clone())
-                .args(args)
-                .output(ExecCommandOutput::builder().stdout(Stdio::piped()).build())
-                .build(),
-        };
+        let config = ExecCommandConfig::builder()
+            .exe(self.exe.clone())
+            .args(args)
+            .output(ExecCommandOutput::builder().stdout(Stdio::piped()).build())
+            .build();
 
         let output = exec_impl
             .exec_command(config)
@@ -138,23 +61,12 @@ impl SupergraphBinary {
             });
         }
 
-        let output = match output_target {
-            OutputTarget::File(path) => {
-                read_file_impl
-                    .read_file(path)
-                    .await
-                    .map_err(|err| CompositionError::ReadFile {
-                        path: path.clone(),
-                        error: Box::new(err),
-                    })?
-            }
-            OutputTarget::Stdout | OutputTarget::InMemory => std::str::from_utf8(&output.stdout)
-                .map_err(|err| CompositionError::InvalidOutput {
-                    binary: self.exe.clone(),
-                    error: format!("{:?}", err),
-                })?
-                .to_string(),
-        };
+        let output = std::str::from_utf8(&output.stdout)
+            .map_err(|err| CompositionError::InvalidOutput {
+                binary: self.exe.clone(),
+                error: format!("{:?}", err),
+            })?
+            .to_string();
 
         self.validate_composition(&output)
     }
@@ -213,8 +125,6 @@ impl SupergraphBinary {
 #[cfg(test)]
 mod tests {
     use std::{
-        fs,
-        path::PathBuf,
         process::{ExitStatus, Output},
         str::FromStr,
     };
@@ -224,29 +134,20 @@ mod tests {
     use assert_fs::TempDir;
     use camino::Utf8PathBuf;
     use houston::Config;
-    use mockall::predicate;
     use rstest::{fixture, rstest};
     use semver::Version;
     use speculoos::prelude::*;
 
-    use super::{CompositionSuccess, OutputTarget, SupergraphBinary};
+    use super::{CompositionSuccess, SupergraphBinary};
     use crate::{
         command::supergraph::compose::do_compose::SupergraphComposeOpts,
         composition::{supergraph::version::SupergraphVersion, test::default_composition_json},
         utils::{
             client::{ClientBuilder, ClientTimeout, StudioClientConfig},
-            effect::{exec::MockExecCommand, read_file::MockReadFile},
+            effect::exec::MockExecCommand,
         },
     };
 
-    fn fed_one() -> Version {
-        Version::from_str("1.0.0").unwrap()
-    }
-
-    fn fed_two_eight() -> Version {
-        Version::from_str("2.8.0").unwrap()
-    }
-
     fn fed_two_nine() -> Version {
         Version::from_str("2.9.0").unwrap()
     }
@@ -278,122 +179,42 @@ mod tests {
         )
     }
 
-    #[fixture]
-    fn composition_output() -> CompositionSuccess {
+    fn composition_output(version: Version) -> CompositionSuccess {
         let res = build_result().unwrap();
 
         CompositionSuccess {
             hints: res.hints,
             supergraph_sdl: res.supergraph_sdl,
-            federation_version: FederationVersion::ExactFedTwo(fed_two_eight()),
+            federation_version: FederationVersion::ExactFedTwo(version),
         }
     }
 
     #[rstest]
-    #[case::fed_one(fed_one(), OutputTarget::Stdout)]
-    #[case::fed_one(fed_two_eight(), OutputTarget::Stdout)]
-    #[case::fed_one(fed_two_nine(), OutputTarget::File(Utf8PathBuf::new()))]
-    fn test_output_target_file_align_to_version(
-        #[case] federation_version: Version,
-        #[case] expected_output_target: OutputTarget,
-    ) {
-        let supergraph_version = SupergraphVersion::new(federation_version);
-        let given_output_target = OutputTarget::File(Utf8PathBuf::new());
-        let result_output_target = given_output_target.align_to_version(&supergraph_version);
-        assert_that!(result_output_target).is_equal_to(expected_output_target);
-    }
-
-    #[rstest]
-    #[case::fed_one(fed_one(), OutputTarget::Stdout)]
-    #[case::fed_two_eight(fed_two_eight(), OutputTarget::Stdout)]
-    #[case::fed_two_nine(fed_two_nine(), OutputTarget::Stdout)]
-    fn test_output_target_stdout_align_to_version(
-        #[case] federation_version: Version,
-        #[case] expected_output_target: OutputTarget,
-    ) {
-        let supergraph_version = SupergraphVersion::new(federation_version);
-        let given_output_target = OutputTarget::Stdout;
-        let result_output_target = given_output_target.align_to_version(&supergraph_version);
-        assert_that!(result_output_target).is_equal_to(expected_output_target);
-    }
-
-    #[rstest]
-    #[case::with_output_path(OutputTarget::File(Utf8PathBuf::from_str("some_output_file").unwrap()), vec!["compose", "dummy_supergraph_config_path", "some_output_file"])]
-    #[case::without_output_path(OutputTarget::Stdout, vec!["compose", "dummy_supergraph_config_path"])]
     #[tokio::test]
-    async fn test_prepare_compose_args(
-        #[case] test_output_target: OutputTarget,
-        #[case] expected_args: Vec<&str>,
-        supergraph_config_path: Utf8PathBuf,
-    ) {
-        let supergraph_version = SupergraphVersion::new(fed_two_eight());
-
+    async fn test_compose_success() -> Result<()> {
+        let version = fed_two_nine();
+        let composition_output = composition_output(version.clone());
+        let supergraph_version = SupergraphVersion::new(version);
+        let binary_path = Utf8PathBuf::from_str("/supergraph")?;
         let supergraph_binary = SupergraphBinary::builder()
-            .exe(Utf8PathBuf::from_str("some/binary").unwrap())
+            .exe(binary_path.clone())
             .version(supergraph_version)
             .build();
 
-        let args =
-            supergraph_binary.prepare_compose_args(&test_output_target, &supergraph_config_path);
-
-        assert_eq!(args, expected_args);
-    }
-
-    #[rstest]
-    #[case::output_stdout(OutputTarget::Stdout)]
-    #[case::output_file(OutputTarget::File(Utf8PathBuf::from_str("/tmp/output_dir/output.txt").unwrap()))]
-    #[tokio::test]
-    async fn test_compose_success(
-        #[case] output_target: OutputTarget,
-        composition_output: CompositionSuccess,
-    ) -> Result<()> {
-        let output_dir = PathBuf::from_str("/tmp/output_dir")?;
-        if output_dir.exists() {
-            fs::remove_dir_all(&output_dir)?;
-        }
-        assert_that!(output_dir.exists()).is_false();
-        let supergraph_version = SupergraphVersion::new(fed_two_eight());
-        let binary_path = Utf8PathBuf::from_str("/tmp/supergraph")?;
-
         let mut opts = SupergraphComposeOpts::default();
         opts.plugin_opts.elv2_license_accepter.elv2_license_accepted = Some(true);
 
-        let supergraph_binary = SupergraphBinary::builder()
-            .exe(binary_path.clone())
-            .version(supergraph_version)
-            .build();
-
-        let temp_supergraph_config_path =
-            Utf8PathBuf::from_str("/tmp/target/supergraph_config.yaml")?;
+        let temp_supergraph_config_path = Utf8PathBuf::from_str("/supergraph_config.yaml")?;
 
-        let mut mock_read_file = MockReadFile::new();
-        if let OutputTarget::File(path) = &output_target {
-            mock_read_file
-                .expect_read_file()
-                .times(1)
-                .with(predicate::eq(path.clone()))
-                .returning(|_| Ok(serde_json::to_string(&default_composition_json()).unwrap()));
-        } else {
-            mock_read_file.expect_read_file().times(0);
-        }
         let mut mock_exec = MockExecCommand::new();
 
         mock_exec
             .expect_exec_command()
             .times(1)
-            .withf({
-                let output_target = output_target.clone();
-                move |actual_config| {
-                    let mut expected_args = vec![
-                        "compose".to_string(),
-                        "/tmp/target/supergraph_config.yaml".to_string(),
-                    ];
-                    if let OutputTarget::File(path) = &output_target {
-                        expected_args.push(path.to_string());
-                    }
-                    actual_config.exe() == &binary_path
-                        && actual_config.args() == &Some(expected_args)
-                }
+            .withf(move |actual_config| {
+                let expected_args =
+                    vec!["compose".to_string(), "/supergraph_config.yaml".to_string()];
+                actual_config.exe() == &binary_path && actual_config.args() == &Some(expected_args)
             })
             .returning(move |_| {
                 let stdout = serde_json::to_string(&default_composition_json()).unwrap();
@@ -405,20 +226,9 @@ mod tests {
             });
 
         let result = supergraph_binary
-            .compose(
-                &mock_exec,
-                &mock_read_file,
-                &output_target,
-                temp_supergraph_config_path,
-            )
+            .compose(&mock_exec, temp_supergraph_config_path)
             .await;
 
-        if let OutputTarget::File(path) = output_target {
-            assert_that!(path.parent())
-                .is_some()
-                .matches(|path| path.exists());
-        }
-
         assert_that!(result).is_ok().is_equal_to(composition_output);
 
         Ok(())
diff --git a/src/composition/watchers/composition.rs b/src/composition/watchers/composition.rs
index 452254cd1..39794483f 100644
--- a/src/composition/watchers/composition.rs
+++ b/src/composition/watchers/composition.rs
@@ -12,7 +12,7 @@ use tokio_util::sync::CancellationToken;
 use tracing::{error, info};
 
 use crate::composition::events::CompositionEvent;
-use crate::composition::supergraph::binary::{OutputTarget, SupergraphBinary};
+use crate::composition::supergraph::binary::SupergraphBinary;
 use crate::composition::supergraph::config::error::ResolveSubgraphError;
 use crate::composition::supergraph::config::full::FullyResolvedSupergraphConfig;
 use crate::composition::supergraph::config::resolver::ResolveSupergraphConfigError;
@@ -29,7 +29,6 @@ use crate::composition::{
 use crate::subtask::SubtaskHandleStream;
 use crate::utils::effect::exec::ExecCommand;
 use crate::utils::effect::install::InstallBinary;
-use crate::utils::effect::read_file::ReadFile;
 use crate::utils::effect::write_file::WriteFile;
 
 /// Event to represent an input to the CompositionWatcher, depending on the source the event comes
@@ -47,23 +46,20 @@ pub enum CompositionInputEvent {
 }
 
 #[derive(Builder, Debug)]
-pub struct CompositionWatcher<ExecC, ReadF, WriteF> {
+pub struct CompositionWatcher<ExecC, WriteF> {
     initial_supergraph_config: FullyResolvedSupergraphConfig,
     initial_resolution_errors: BTreeMap<String, ResolveSubgraphError>,
     federation_updater_config: Option<FederationUpdaterConfig>,
     supergraph_binary: Result<SupergraphBinary, InstallSupergraphError>,
     exec_command: ExecC,
-    read_file: ReadF,
     write_file: WriteF,
     temp_dir: Utf8PathBuf,
     compose_on_initialisation: bool,
-    output_target: OutputTarget,
 }
 
-impl<ExecC, ReadF, WriteF> SubtaskHandleStream for CompositionWatcher<ExecC, ReadF, WriteF>
+impl<ExecC, WriteF> SubtaskHandleStream for CompositionWatcher<ExecC, WriteF>
 where
     ExecC: ExecCommand + Send + Sync + 'static,
-    ReadF: ReadFile + Send + Sync + 'static,
     WriteF: WriteFile + Send + Sync + 'static,
 {
     type Input = CompositionInputEvent;
@@ -93,9 +89,7 @@ where
                     let _ = sender
                         .send(CompositionEvent::Started)
                         .tap_err(|err| error!("{:?}", err));
-                    let output = self
-                        .run_composition(&target_file, &self.output_target)
-                        .await;
+                    let output = self.run_composition(&target_file).await;
                     match output {
                         Ok(success) => {
                             let _ = sender
@@ -213,7 +207,7 @@ where
                         .tap_err(|err| error!("{:?}", err));
 
                     let output = self
-                        .run_composition(&target_file, &self.output_target)
+                        .run_composition(&target_file)
                         .await;
 
                     match output {
@@ -236,10 +230,9 @@ where
     }
 }
 
-impl<ExecC, ReadF, WriteF> CompositionWatcher<ExecC, ReadF, WriteF>
+impl<ExecC, WriteF> CompositionWatcher<ExecC, WriteF>
 where
     ExecC: 'static + ExecCommand + Send + Sync,
-    ReadF: 'static + ReadFile + Send + Sync,
     WriteF: 'static + Send + Sync + WriteFile,
 {
     async fn setup_temporary_supergraph_yaml(
@@ -278,17 +271,11 @@ where
     async fn run_composition(
         &self,
         target_file: &Utf8PathBuf,
-        output_target: &OutputTarget,
     ) -> Result<CompositionSuccess, CompositionError> {
         match &self.supergraph_binary {
             Ok(binary) => {
                 binary
-                    .compose(
-                        &self.exec_command,
-                        &self.read_file,
-                        output_target,
-                        target_file.clone(),
-                    )
+                    .compose(&self.exec_command, target_file.clone())
                     .await
             }
             Err(err) => Err(CompositionError::InstallSupergraphBinaryError {
@@ -318,7 +305,7 @@ mod tests {
 
     use super::{CompositionInputEvent, CompositionWatcher};
     use crate::composition::events::CompositionEvent;
-    use crate::composition::supergraph::binary::{OutputTarget, SupergraphBinary};
+    use crate::composition::supergraph::binary::SupergraphBinary;
     use crate::composition::supergraph::config::full::FullyResolvedSupergraphConfig;
     use crate::composition::supergraph::version::SupergraphVersion;
     use crate::composition::test::{default_composition_json, default_composition_success};
@@ -401,11 +388,9 @@ mod tests {
             .initial_supergraph_config(subgraphs)
             .supergraph_binary(Ok(supergraph_binary))
             .exec_command(mock_exec)
-            .read_file(mock_read_file)
             .write_file(mock_write_file)
             .temp_dir(temp_dir_path)
             .compose_on_initialisation(false)
-            .output_target(OutputTarget::Stdout)
             .build();
 
         let subgraph_change_events: BoxStream<CompositionInputEvent> = once(async {


Test patch: