Repository navigation
feat: save the debug log to the SD card, and quieten the Dropbox 409 - #22
Merged
Merged
Conversation
A debug run is read off a 30-line screen that scrolls, so by the time it reaches whatever went wrong, the version, the init log and the config summary have gone off the top. Photographing the screen is what a bug report ended up being made of. Everything written to the console is now also captured while debug mode is on, and the end of the run offers to write it to the SD card as /3ds/3DSync/logs/debug-<stamp>.log. The capture is fed by teeing stdout rather than by routing every printf through debugf(): libctru's console is a newlib devoptab, so a copy of it with write_r wrapped records the run exactly as it appeared. It has to be installed after the last consoleInit(), which replaces devoptab_list[STD_OUT] with its own. Only the first 96 KB and the last 48 KB are kept — a long run drops its middle, and says in the file how much. debugLogSave() strips the ANSI sequences and runs debugRedact() over the finished text rather than per write, because a token can straddle two console writes. The save is offered, not done: the file is plaintext on a shared SD card and the run it came from listed every save path on the console, so the prompt says as much. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every sync printed a red
Dropbox API error: HTTP 409
{"error":{".tag":"path","path":{".tag":"conflict",...}}}
between the entry header and "Listing", then carried on and worked.
ensureRoot() called create_folder_v2 on every run and already treated
409 as success, but _performWithRetry() had printed the error before the
caller ever saw the status.
_rpc() / _performWithRetry() now take an expectedStatus: the status is
still returned and still explained through debugf(), but the console
line is suppressed. Where the expectation turns out to be wrong, the
caller prints the reason itself via _report409Reason().
ensureRoot() now probes with get_metadata and only creates when the
probe says path/not_found. The folder exists on every run but the
first, so that trades a write that 409s for a read that succeeds, at the
cost of one extra round trip on the first run. In exchange the steady
state is read-only, and whether the path is a folder comes from a .tag
field rather than from the wording of an error.
That also fixes a latent bug: any 409 was previously swallowed as
success, so a file sitting where the sync folder belongs, a malformed
Path= and a missing write permission all "succeeded" here and failed
later. Dropbox separates those only by error_summary, so the reading of
it lives in remoteparse.cpp as dropboxPathState() — testable on the host
against the real response bodies — rather than inline in the provider.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dropbox reached parity with Drive in #19 and has been tested thoroughly since: it verifies by content hash, resolves conflicts, uploads through a temp path and refreshes its own token, which is everything Drive does. isExperimental() defaults to true so a new backend cannot forget the notice, so Dropbox now opts out explicitly, and the yellow "support is EXPERIMENTAL" banner no longer appears before a Dropbox sync. The README table, the landing page and the configurator drop the badge with it; SMB, FTP and WebDAV keep theirs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
v0.5.1 is tagged, and this branch adds a user-facing feature (saving the debug log) and promotes Dropbox out of experimental, so the minor moves rather than the micro. Per the release process in version.h, this is the first of the two steps: the tag comes after this lands on master, and release.yml's Version Check fails the release if the two disagree. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
michvllni
force-pushed
the
feat/debug-log-and-dropbox-409
branch
from
September 1, 2026 06:59
12b3f1a to
324b2a2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two issues from a real Dropbox sync, plus the follow-on cleanup.
1. Debug mode now offers to save the log
A debug run is read off a 30-line screen that scrolls, so by the time it reaches whatever went wrong, the version, the init log and the config summary are long gone. Photographing the screen is what a bug report ended up being made of.
Everything written to the console is now captured while debug mode is on, and the end of the run offers to write it to
/3ds/3DSync/logs/debug-<stamp>.log— A saves, B discards.The capture is fed by teeing stdout rather than by routing every
printfthroughdebugf(): libctru's console is a newlib devoptab, so a copy of it withwrite_rwrapped records the run exactly as it appeared. It has to be installed after the lastconsoleInit(), which replacesdevoptab_list[STD_OUT]with its own.debugLogSave()strips ANSI sequences and runsdebugRedact()over the finished text, not per write — a token can straddle two console writes.2. The spurious
Dropbox API error: HTTP 409Every sync printed a red 409 with a
path/conflict/folderbody between the entry header andListing, then carried on and worked.ensureRoot()calledcreate_folder_v2on every run and already treated 409 as success — but_performWithRetry()printed the error before the caller ever saw the status._rpc()/_performWithRetry()now take anexpectedStatus: the status is still returned and still explained throughdebugf(), but the console line is suppressed. Where the expectation turns out to be wrong, the caller prints the reason itself.ensureRoot()now probes withget_metadataand only creates when the probe sayspath/not_found. Request count is a wash — the folder exists on every run but the first, so it trades a write that 409s for a read that succeeds, at the cost of one extra round trip on the first run. In exchange the steady state is read-only, and "is this a folder" comes from a.tagfield rather than the wording of an error.That also fixes a latent bug: any 409 was previously swallowed as success, so a file sitting where the sync folder belongs, a malformed
Path=and a missing write permission all "succeeded" here and failed later. Dropbox separates those only byerror_summary, so the reading of it lives inremoteparse.cppasdropboxPathState()— host-testable against the real response bodies — rather than inline in the provider.No new OAuth scope:
files.metadata.readwas already required bylist_folder.3. Dropbox is no longer marked experimental
Per @michvllni, tested thoroughly.
isExperimental()defaults totrueso a new backend cannot forget the notice, so Dropbox opts out explicitly. README table, landing page and configurator drop the badge; SMB, FTP and WebDAV keep theirs.4. Version
APP_VERSION0.5.1 → 0.6.0. v0.5.1 is already tagged, and this branch adds a user-facing feature and promotes a backend out of experimental, so the minor moves. Per the release process inversion.hthis is step one; the tag comes after this lands on master, andrelease.yml's Version Check fails the release if the two disagree.5. Devcontainer — split out to #23
The
gh/ extension change was originally here, butpr.ymlrebuilds the toolchain image from scratch whenever a PR touchesdocker/*Dockerfile, andpkg.devkitpro.orgis currently behind a Cloudflare block (HTTP 403 ondkp-linux.db). That is a third-party outage, not a code problem, but it would keep this PR red for as long as it lasts — so it now lives in #23 and this PR takes thedocker pullpath again.Testing
makebuilds clean for the 3DS.make -C tests: 115 passed, 0 failed (was 101). Each commit builds and passes in isolation.debuglogcases: the capture must stay empty outside debug mode, stay bounded on a long run, reset between runs, and reach the SD card with colour codes gone and tokens masked.dropboxPathStatecases pinning each real Dropbox response body — including thatpath/malformed_pathandpath/no_write_permissionare not read as success, and that an unparseable 2xx is not treated as a verdict.Not exercised on hardware — the devoptab tee and the SD write are the parts worth a real-console check before merge.
🤖 Generated with Claude Code