From 9a95e114f80934a253e22f91ac471c78c0fc6698 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 21 Jul 2026 11:26:47 +0200 Subject: [PATCH 1/3] fix(ndk): Create outbox directory before writing crash envelopes The NDK layer writes crash envelopes through a custom transport that targets the head SDK's outbox path. Native init only creates its own `.sentry-native` database directory (a sibling of the outbox), and the envelope write does not create parent directories, so a missing outbox made the write fail silently and the crash report was lost. Create the outbox in the transport before each write, matching how the rest of sentry-native ensures its cache and external directories exist, and log if creation fails so the failure is diagnosable. This lets head SDKs create the outbox lazily without dropping native crash envelopes. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 1 + ndk/lib/src/main/jni/sentry.c | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3494f6f76..cab90eb6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ **Fixes**: - Crashpad: wait reliably for crash report uploads. ([#1885](https://github.com/getsentry/sentry-native/pull/1885)) +- Android: create the outbox directory before writing NDK crash envelopes into it, so envelopes are not lost when the head SDK creates the outbox lazily. ([#1889](https://github.com/getsentry/sentry-native/pull/1889)) ## 0.15.4 diff --git a/ndk/lib/src/main/jni/sentry.c b/ndk/lib/src/main/jni/sentry.c index 2af6fc9cf..b50acdd25 100644 --- a/ndk/lib/src/main/jni/sentry.c +++ b/ndk/lib/src/main/jni/sentry.c @@ -308,6 +308,16 @@ Java_io_sentry_ndk_NativeScope_nativeClearAttachments(JNIEnv *env, jclass cls) sentry_clear_attachments(); } +// sentry_path.h +struct sentry_path_s; +extern struct sentry_path_s *sentry__path_from_str(const char *s); +extern int sentry__path_create_dir_all(const struct sentry_path_s *path); +extern void sentry__path_free(struct sentry_path_s *path); + +// sentry_logger.h +extern void sentry__logger_log( + sentry_level_t level, const char *message, ...); + static void send_envelope(sentry_envelope_t *envelope, void *data) { @@ -317,6 +327,18 @@ send_envelope(sentry_envelope_t *envelope, void *data) sentry_uuid_t envelope_id = sentry_uuid_new_v4(); sentry_uuid_as_string(&envelope_id, envelope_id_str); + // The head SDK may create the outbox lazily, so ensure it exists before + // writing into it; the underlying file write does not create parent + // directories and would otherwise silently fail. + struct sentry_path_s *outbox = sentry__path_from_str(outbox_path); + if (outbox) { + if (sentry__path_create_dir_all(outbox) != 0) { + sentry__logger_log(SENTRY_LEVEL_ERROR, + "failed to create outbox directory \"%s\"", outbox_path); + } + sentry__path_free(outbox); + } + size_t outbox_len = strlen(outbox_path); size_t final_len = outbox_len + 42; // "/" + envelope_id_str + "\0" = 42 char *envelope_path = sentry_malloc(final_len); From 725e730aeb630c4fa39303962b36ffd0854b7694 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 21 Jul 2026 11:26:47 +0200 Subject: [PATCH 2/3] docs(ndk): Drop manual outbox mkdir from the sample sentry-native now creates the outbox directory itself before writing, so the sample no longer needs to create it up front. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../io/sentry/ndk/sample/MainActivity.java | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java index fdef19d5a..92d07b63d 100644 --- a/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java +++ b/ndk/sample/src/main/java/io/sentry/ndk/sample/MainActivity.java @@ -19,7 +19,9 @@ protected void onCreate(Bundle savedInstanceState) { } private void initNdk() { - final File outboxFolder = setupOutboxFolder(); + // The outbox directory is created by sentry-native, so we only need to + // hand it the path here. + final File outboxFolder = new File(getFilesDir(), "outbox"); final NdkOptions options = new NdkOptions( "https://1053864c67cc410aa1ffc9701bd6f93d@o447951.ingest.sentry.io/5428559", @@ -34,22 +36,4 @@ private void initNdk() { options.setTracesSampleRate(1); SentryNdk.init(options); } - - private File setupOutboxFolder() { - // ensure we have a proper outbox directory - final File outboxDir = new File(getFilesDir(), "outbox"); - if (outboxDir.isFile()) { - final boolean deleteOk = outboxDir.delete(); - if (!deleteOk) { - throw new IllegalStateException("Failed to delete outbox file: " + outboxDir); - } - } - if (!outboxDir.exists()) { - final boolean mkdirOk = outboxDir.mkdirs(); - if (!mkdirOk) { - throw new IllegalStateException("Failed to create outbox directory: " + outboxDir); - } - } - return outboxDir; - } } From f212c7301e9de191c80a7ffa2a99a36d86edd701 Mon Sep 17 00:00:00 2001 From: Nelson Osacky Date: Tue, 21 Jul 2026 16:24:15 +0200 Subject: [PATCH 3/3] fix(ndk): Create outbox with mkdir instead of core internals The previous approach called sentry__path_* and sentry__logger_log via extern declarations. Those symbols are internal to the core library and, unlike SENTRY_API-tagged ones, are not exported from the shared object the NDK links against, so the Android build failed to link with undefined symbol errors. Create the outbox directory directly with mkdir(2) instead. The NDK library is Android-only, so POSIX is always available, and log failures through __android_log_print rather than the unexported core logger. Co-Authored-By: Claude Opus 4.8 (1M context) --- ndk/lib/CMakeLists.txt | 5 +++- ndk/lib/src/main/jni/sentry.c | 51 ++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 16 deletions(-) diff --git a/ndk/lib/CMakeLists.txt b/ndk/lib/CMakeLists.txt index 9bef5d7b3..1b4153eda 100644 --- a/ndk/lib/CMakeLists.txt +++ b/ndk/lib/CMakeLists.txt @@ -27,7 +27,10 @@ if(ENABLE_TESTS) endif() # Link to sentry-native -target_link_libraries(sentry-android PRIVATE $) +target_link_libraries(sentry-android PRIVATE + ${LOG_LIB} + $ +) # Support 16KB page sizes target_link_options(sentry-android PRIVATE "-Wl,-z,max-page-size=16384") diff --git a/ndk/lib/src/main/jni/sentry.c b/ndk/lib/src/main/jni/sentry.c index b50acdd25..8235a7782 100644 --- a/ndk/lib/src/main/jni/sentry.c +++ b/ndk/lib/src/main/jni/sentry.c @@ -1,8 +1,11 @@ +#include +#include #include #include #include #include #include +#include #define ENSURE(Expr) \ if (!(Expr)) \ @@ -308,15 +311,37 @@ Java_io_sentry_ndk_NativeScope_nativeClearAttachments(JNIEnv *env, jclass cls) sentry_clear_attachments(); } -// sentry_path.h -struct sentry_path_s; -extern struct sentry_path_s *sentry__path_from_str(const char *s); -extern int sentry__path_create_dir_all(const struct sentry_path_s *path); -extern void sentry__path_free(struct sentry_path_s *path); +// sentry-native's path helpers are internal to the core library and not +// exported from the shared object we link against, so we create the outbox +// ourselves. Android is always POSIX, so mkdir(2) is all we need. +static int +create_dir_all(const char *path) +{ + char *buf = sentry_malloc(strlen(path) + 1); + if (!buf) { + return -1; + } + strcpy(buf, path); -// sentry_logger.h -extern void sentry__logger_log( - sentry_level_t level, const char *message, ...); + int rv = 0; + for (char *p = buf; *p; p++) { + if (*p == '/' && p != buf) { + *p = '\0'; + if (mkdir(buf, 0700) != 0 && errno != EEXIST && errno != EINVAL) { + rv = -1; + goto done; + } + *p = '/'; + } + } + if (mkdir(buf, 0700) != 0 && errno != EEXIST && errno != EINVAL) { + rv = -1; + } + +done: + sentry_free(buf); + return rv; +} static void send_envelope(sentry_envelope_t *envelope, void *data) @@ -330,13 +355,9 @@ send_envelope(sentry_envelope_t *envelope, void *data) // The head SDK may create the outbox lazily, so ensure it exists before // writing into it; the underlying file write does not create parent // directories and would otherwise silently fail. - struct sentry_path_s *outbox = sentry__path_from_str(outbox_path); - if (outbox) { - if (sentry__path_create_dir_all(outbox) != 0) { - sentry__logger_log(SENTRY_LEVEL_ERROR, - "failed to create outbox directory \"%s\"", outbox_path); - } - sentry__path_free(outbox); + if (create_dir_all(outbox_path) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "sentry-native", + "failed to create outbox directory \"%s\"", outbox_path); } size_t outbox_len = strlen(outbox_path);