diff --git a/src/os/cpath/cpath.c b/src/os/cpath/cpath.c
index 02057c4..a4cdc4e 100644
--- a/src/os/cpath/cpath.c
+++ b/src/os/cpath/cpath.c
@@ -1,5 +1,6 @@
 #include "cpath.h"
 #include "aliases.h"
+#include "str8.h"
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -7,16 +8,48 @@
 
 void join_root_and_leaf(const char *root, const char *leaf, char *dst);
 
-void join_path(char *dst, u64 count, ...) {
-  va_list args;
-
-  va_start(args, count);
-
-  for (u64 i = 0; i < count; ++i) {
-    join_root_and_leaf(dst, va_arg(args, const char *), dst);
+u32 wapp_cpath_join_path(Str8 *dst, const Str8List *parts) {
+  if (!dst || !parts) {
+    return CPATH_JOIN_INVALID_ARGS;
   }
 
-  va_end(args);
+  if (parts->node_count == 0) {
+    return CPATH_JOIN_EMPTY_PARTS;
+  }
+
+  Str8 separator = wapp_str8_buf(4);
+  wapp_str8_push_back(&separator, PATH_SEP);
+
+  u64 required_capacity = parts->node_count * separator.size + parts->total_size;
+  if (dst->capacity < required_capacity) {
+    return CPATH_JOIN_INSUFFICIENT_DST_CAPACITY;
+  }
+
+  // Handle first node
+  const Str8Node *first_node = wapp_str8_list_get(parts, 0);
+  wapp_str8_copy_str8_capped(dst, first_node->string);
+
+  const Str8Node *node = first_node;
+  for (u64 i = 1; i < parts->node_count; ++i) {
+    node = node->next;
+    if (node->string->size == 0) {
+      continue;
+    }
+
+    if (dst->size > 0) {
+      char dst_last     = wapp_str8_get(dst, dst->size - 1);
+      char node_start   = wapp_str8_get(node->string, 0);
+      bool add_path_sep = dst_last != PATH_SEP && node_start != PATH_SEP;
+
+      if (add_path_sep) {
+        wapp_str8_concat_capped(dst, &separator);
+      }
+    }
+
+    wapp_str8_concat_capped(dst, node->string);
+  }
+
+  return CPATH_JOIN_SUCCESS;
 }
 
 void dirup(char *dst, u64 levels, const char *path) {
diff --git a/src/os/cpath/cpath.h b/src/os/cpath/cpath.h
index 13d565c..f3c1ee5 100644
--- a/src/os/cpath/cpath.h
+++ b/src/os/cpath/cpath.h
@@ -3,6 +3,7 @@
 
 #include "aliases.h"
 #include "platform.h"
+#include "str8.h"
 
 #ifdef __cplusplus
 BEGIN_C_LINKAGE
@@ -16,13 +17,17 @@ BEGIN_C_LINKAGE
 #error "Unrecognised platform"
 #endif
 
-#define NUMPARTS(...) (sizeof((const char *[]){"", __VA_ARGS__}) / sizeof(const char *) - 1)
-
-#define wapp_cpath_join_path(DST, ...) join_path(DST, NUMPARTS(__VA_ARGS__), __VA_ARGS__)
 #define wapp_cpath_dirname(DST, PATH) dirup(DST, 1, PATH)
 #define wapp_cpath_dirup(DST, COUNT, PATH) dirup(DST, COUNT, PATH)
 
-void join_path(char *dst, u64 count, ...);
+enum {
+  CPATH_JOIN_SUCCESS = 0,
+  CPATH_JOIN_INVALID_ARGS,
+  CPATH_JOIN_EMPTY_PARTS,
+  CPATH_JOIN_INSUFFICIENT_DST_CAPACITY,
+};
+
+u32  wapp_cpath_join_path(Str8 *dst, const Str8List *parts);
 void dirup(char *dst, u64 levels, const char *path);
 
 #ifdef __cplusplus
diff --git a/tests/cpath/test_cpath.c b/tests/cpath/test_cpath.c
index e112c33..d7c5db3 100644
--- a/tests/cpath/test_cpath.c
+++ b/tests/cpath/test_cpath.c
@@ -1,5 +1,6 @@
 #include "test_cpath.h"
 #include "cpath.h"
+#include "str8.h"
 #include "tester.h"
 #include <string.h>
 #include <stdio.h>
@@ -9,77 +10,76 @@
 #define TMP_BUF_SIZE  1024
 
 TestFuncResult test_cpath_join_path(void) {
-  char expected[MAIN_BUF_SIZE] = {0};
-  char out[MAIN_BUF_SIZE]      = {0};
-  char tmp[TMP_BUF_SIZE]      = {0};
+  bool result;
 
-  snprintf(expected, MAIN_BUF_SIZE, "%chome%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP, PATH_SEP);
-  snprintf(tmp, 2, "%c", PATH_SEP);
-  wapp_cpath_join_path(out, tmp, "home", "abdelrahman", "Documents");
+  Str8 expected = wapp_str8_buf(MAIN_BUF_SIZE);
+  Str8 out      = wapp_str8_buf(MAIN_BUF_SIZE);
+  Str8 tmp      = wapp_str8_buf(TMP_BUF_SIZE);
 
-  bool result = strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_str8_format(&expected, "%chome%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP, PATH_SEP);
+  wapp_str8_format(&tmp, "%c", PATH_SEP);
 
-  memset(out, 0, strlen(out));
-  snprintf(expected, MAIN_BUF_SIZE, "home%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP);
-  wapp_cpath_join_path(out, "home", "abdelrahman", "Documents");
+  Str8List parts = {0};
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_str8(tmp));
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr("home"));
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr("abdelrahman"));
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr("Documents"));
 
-  result = result && strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_cpath_join_path(&out, &parts);
+  result = wapp_str8_equal(&out, &expected);
 
-  memset(out, 0, strlen(out));
-  memset(tmp, 0, strlen(tmp));
-  snprintf(expected, MAIN_BUF_SIZE, "%chome%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP, PATH_SEP);
-  snprintf(tmp, TMP_BUF_SIZE, "%chome", PATH_SEP);
-  wapp_cpath_join_path(out, tmp, "abdelrahman", "Documents");
+  wapp_str8_list_pop_front(&parts);
 
-  result = result && strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_str8_format(&expected, "home%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP);
 
-  memset(out, 0, strlen(out));
-  memset(tmp, 0, strlen(tmp));
-  snprintf(expected, MAIN_BUF_SIZE, "home%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP);
-  snprintf(tmp, TMP_BUF_SIZE, "home%c", PATH_SEP);
-  wapp_cpath_join_path(out, tmp, "abdelrahman", "Documents");
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
 
-  result = result && strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_str8_concat_capped(&tmp, &wapp_str8_lit_ro("home"));
+  wapp_str8_list_pop_front(&parts);
+  wapp_str8_list_push_front(&parts, &wapp_str8_node_from_str8(tmp));
 
-  memset(out, 0, strlen(out));
-  memset(tmp, 0, strlen(tmp));
-  snprintf(expected, MAIN_BUF_SIZE, "%chome", PATH_SEP);
-  snprintf(tmp, TMP_BUF_SIZE, "%chome", PATH_SEP);
-  wapp_cpath_join_path(out, tmp, "");
+  wapp_str8_format(&expected, "%chome%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP, PATH_SEP);
 
-  result = result && strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
 
-  memset(out, 0, strlen(out));
-  snprintf(expected, 1, "%s", "");
-  wapp_cpath_join_path(out, "", "");
+  wapp_str8_format(&tmp, "home%c", PATH_SEP);
+  wapp_str8_list_pop_front(&parts);
+  wapp_str8_list_push_front(&parts, &wapp_str8_node_from_cstr("home"));
 
-  result = result && strcmp(out, expected) == 0;
-  if (!result) {
-    goto TEST_JOIN_PATH_EXIT;
-  }
+  wapp_str8_format(&expected, "home%cabdelrahman%cDocuments", PATH_SEP, PATH_SEP);
 
-  memset(out, 0, strlen(out));
-  snprintf(expected, MAIN_BUF_SIZE, "home");
-  wapp_cpath_join_path(out, "", "home");
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
 
-  result = result && strcmp(out, expected) == 0;
+  wapp_str8_list_empty(&parts);
+
+  wapp_str8_format(&tmp, "%chome", PATH_SEP);
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_str8(tmp));
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr(""));
+
+  wapp_str8_format(&expected, "%chome", PATH_SEP);
+
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
+
+  wapp_str8_list_pop_front(&parts);
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr(""));
+
+  wapp_str8_format(&expected, "%s", "");
+
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
+
+  wapp_str8_list_pop_back(&parts);
+  wapp_str8_list_push_back(&parts, &wapp_str8_node_from_cstr("home"));
+
+  wapp_str8_copy_cstr_capped(&expected, "home");
+
+  wapp_cpath_join_path(&out, &parts);
+  result = result && wapp_str8_equal(&out, &expected);
 
-TEST_JOIN_PATH_EXIT:
   return wapp_tester_result(result);
 }
 
diff --git a/tests/wapptest.c b/tests/wapptest.c
index 77b6fb1..30c6b0a 100644
--- a/tests/wapptest.c
+++ b/tests/wapptest.c
@@ -44,6 +44,7 @@ int main(void) {
                         test_str8_list_pop_front,
                         test_str8_list_pop_back,
                         test_str8_list_remove,
+                        test_str8_list_empty,
                         test_cpath_join_path,
                         test_cpath_dirname,
                         test_cpath_dirup,