diff --git a/compiler/bin/main.ml b/compiler/bin/main.ml
index bf40073..67b502e 100644
--- a/compiler/bin/main.ml
+++ b/compiler/bin/main.ml
@@ -878,6 +878,16 @@ let build_mode ?(deps : (string * string) list = []) ~(runtime : string option)
Printf.eprintf "woc: %s\n" msg;
exit 2
in
+ (* a fresh checkout has no target/ directories: create the output's
+ parent, so `-o
/` works the way every gate and README
+ invokes it instead of failing on the temp file below *)
+ let rec mkdir_p d =
+ if d <> "" && d <> "." && d <> "/" && not (Sys.file_exists d) then begin
+ mkdir_p (Filename.dirname d);
+ (try Sys.mkdir d 0o755 with Sys_error _ -> ())
+ end
+ in
+ mkdir_p (Filename.dirname out);
let tmp = out ^ ".woc-build.tmp" in
(* stale tmp from an interrupted earlier build must not survive: its
permission bits would leak through, since Open_creat on an
diff --git a/scripts/single-binary-smoke.sh b/scripts/single-binary-smoke.sh
index 5a4eb33..bcb47ce 100755
--- a/scripts/single-binary-smoke.sh
+++ b/scripts/single-binary-smoke.sh
@@ -62,6 +62,21 @@ else
ok "single-binary/build"
fi
+# ---- -o into a directory that does not exist yet ---------------------------
+# A fresh checkout has no target/ anywhere (gitignored), and every example
+# gate builds with `-o /target/`: `woc build` must create the
+# output's parent rather than fail on its own temp file there.
+FRESH="$WORK/fresh/target/app"
+if ! timeout "$TIMEOUT" "$WOC" build "$SRC" -o "$FRESH" --runtime "$WOVM" \
+ >"$WORK/fresh.out" 2>"$WORK/fresh.err"; then
+ rc=$?
+ bad "single-binary/build-into-missing-dir" "woc build exited $rc: $(head -1 "$WORK/fresh.err")"
+elif [[ ! -x "$FRESH" ]]; then
+ bad "single-binary/build-into-missing-dir" "output missing or not executable: $FRESH"
+else
+ ok "single-binary/build-into-missing-dir"
+fi
+
# ---- relocate outside the repo, run with no args, diff --------------------
if [[ -x "$APP" ]]; then
cp "$APP" "$ELSEWHERE/app"