fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: do not lose island progress when the server restarts - #18

Merged
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress
Aug 8, 2026
Merged

fix: do not lose island progress when the server restarts#18
tastybento merged 2 commits into
developfrom
fix/shutdown-save-progress

Conversation

@tastybento

Copy link
Copy Markdown
Member

Ported from AOneBlock, which this addon is forked from. ChunkBlock carries the bug verbatim — same code, same line numbers. See BentoBoxWorld/AOneBlock#550 for the original report.

The bug

Reported on Discord (against AOneBlock): a player breaks 30 blocks, the server restarts while they are still online, and they come back with those 30 blocks un-broken. Every restart, repeatedly.

The shutdown save was queued rather than written. ChunkBlock.onDisable() called saveCache(), which uses saveObjectAsync(). This addon is a Pladdon, so the server disables it before BentoBox — the write went into a queue that BentoBox had to drain on its way out. BentoBox 3.22.0 added that drain (AbstractDatabaseHandler.flushAll()), but every earlier version discarded it silently.

With the shutdown save lost, the count fell back to the last periodic checkpoint, which was a hardcoded every-50-blocks. Break fewer than 50 between restarts and nothing persists at all.

The fix

Write directly on shutdown. New BlockListener.saveCacheNow() using saveObjectNow(), called from onDisable(). This removes the dependency on core behaviour rather than relying on a particular BentoBox version getting it right. saveCache() is unchanged and still used for onReload(), where async is correct.

island.save-every, default 10 (was a hardcoded SAVE_EVERY = 50). Nothing helps if the server is SIGKILLed — there is no shutdown path to run — but this caps what an unclean kill can cost at 9 blocks instead of 49. getSaveEvery() clamps to ≥1 since it is a modulo divisor.

⚠️ Minimum BentoBox version is now 3.22.0

saveObjectNow() is 3.22.0 API, so bentobox.version is bumped and addon.ymlapi-version goes 3.13.0 → 3.22.0.

The api-version bump is required, not cosmetic. Without it the addon would still load on an older core and throw NoSuchMethodError at shutdown — worse than the bug being fixed. With it, BentoBox refuses to load the addon and says NOTE: Please update BentoBox.

Verification

Full suite: 620 tests, 0 failures.

Scope caveat: unlike the AOneBlock PR, this port was not boot-tested on a real server. The equivalent AOneBlock change was verified on two (BentoBox 3.15.1 refuses to load it with a clear message and no linkage error; 3.22.1-SNAPSHOT enables, runs and disables cleanly), and the code here is identical, but ChunkBlock itself has only been checked against its test suite.

Test fallout

The BentoBox bump broke the same 5 PhasesPanelTest tests it broke in AOneBlock, at the same line numbers. None were real regressions. All called when(user.getTranslation(...)) on a real User (from User.getInstance(mockPlayer)), which stubs nothing on the User — Mockito attaches the stub to whichever mock the real method last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon(world) first, moving the target.

Ported the stubTranslation() helper that stubs the LocalesManager these actually read from.

Follow-up worth doing: roughly a dozen more when(user.getTranslation(...)) calls remain in that class, passing today by the same accident. They will break on some future BentoBox internal change.

🤖 Generated with Claude Code

https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ

tastybentoand others added 2 commits August 8, 2026 12:35
Ported from AOneBlock, which this addon is forked from and shares the bug
with verbatim - same code, same line numbers.
See BentoBoxWorld/AOneBlock#550.
A player breaking blocks and then sitting through a restart would come back
to their block count rolled back to the last checkpoint - up to 49 blocks of
progress gone, repeatedly, on every restart.
The shutdown save was queued, not written. onDisable() called saveCache(),
which uses saveObjectAsync(), and this addon is a Pladdon: the server disables
it before BentoBox, so the write landed in a queue that BentoBox had to drain
on its way out. BentoBox 3.22.0 added that drain (flushAll), but every earlier
version discarded it silently.
Write directly on shutdown instead, via a new saveCacheNow() using
saveObjectNow(). That removes the dependency on core behaviour entirely rather
than relying on a specific BentoBox version getting it right.
saveObjectNow() is BentoBox 3.22.0 API, so bump the dependency and raise
api-version to match. Without the api-version bump the addon would still load
on an older core and throw NoSuchMethodError at shutdown, which is worse than
the bug being fixed. It now refuses to load with "Please update BentoBox".
Also make the periodic save interval configurable as island.save-every,
defaulting to 10 rather than the hardcoded 50. Nothing helps if the server is
SIGKILLed, but this caps what an unclean kill can cost at 9 blocks.
Test notes: the BentoBox bump broke the same 5 PhasesPanelTest tests it broke
in AOneBlock, at the same line numbers. None were regressions - all called
when(user.getTranslation(...)) on a real User rather than a mock, which stubs
nothing on the User and instead attaches to whichever mock the real method
last touched. 3.22.0's getTranslation(World, ...) calls getIWM().getAddon()
first, moving the target. Ported the stubTranslation() helper that stubs the
LocalesManager these actually read from. The same pattern remains elsewhere in
that class and is worth a follow-up sweep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
BentoBox 3.18.0 onwards is compiled for Java 25 (Minecraft 26.x), so its class
files are version 69. A JDK 21 javac cannot parse those at all, and the build
dies with "class file has wrong version 69.0, should be 65.0" against every
BentoBox type before it reaches any of our code.
This only surfaces once the dependency moves to 3.22.0, as it does in this
branch. It did not show up locally because the dev machine is already on
JDK 25 - the compiler reads the newer class files happily and
<release>21</release> still emits Java 21 bytecode, which is what the addon
ships.
The addon's own target is unchanged: still Java 21 via <release> in the pom.
Only the JDK doing the compiling moves.
Also moves setup-java to v4 and the 'adopt' distribution to 'temurin', since
neither v3 nor adopt offers a Java 25 build.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014t1DSo2wMbTWZLcwXpwUmQ
@tastybento
tastybento merged commit 366a5fe into developAug 8, 2026
1 check passed
@tastybento
tastybento deleted the fix/shutdown-save-progress branch August 8, 2026 19:54
@sonarqubecloud

Copy link
Copy Markdown

@tastybentotastybento mentioned this pull request Aug 8, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@tastybento