Skip to content

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

@henrywang
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
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;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix: stop silently discarding a user's config on decode failure by henrywang · Pull Request #90 · henrywang/Berthly · GitHub
Skip to content

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

@henrywang
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: stop silently discarding a user's config on decode failure by henrywang · Pull Request #90 · henrywang/Berthly · GitHub
Skip to content

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

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

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

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

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

@henrywang
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: stop silently discarding a user's config on decode failure by henrywang · Pull Request #90 · henrywang/Berthly · GitHub
Skip to content

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

@henrywang
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix: stop silently discarding a user's config on decode failure by henrywang · Pull Request #90 · henrywang/Berthly · GitHub
Skip to content

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

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

fix: stop silently discarding a user's config on decode failure - #90

Merged
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard
Aug 4, 2026
Merged

fix: stop silently discarding a user's config on decode failure#90
henrywang merged 1 commit into
mainfrom
fix-silent-config-discard

Conversation

@henrywang

Copy link
Copy Markdown
Owner

Summary

  • resolvedSystemConfig() used try? around ConfigurationLoader.load(), so any decode failure silently fell back to an all-defaults ContainerSystemConfig — not just the field that failed, the user's entire config.toml (DNS, build settings, kernel, everything).
  • ConfigurationLoader.load() only throws when a config file exists on disk but fails to parse/decode; it already returns cleanly with defaults when no file is present at all — so every error caught here represents real discarded user data, not an absent-file no-op.
  • container 1.2.0 made this concrete: KernelConfig's decoder now throws when kernel.url is customized without a paired kernel.digest (Verify kernel archive integrity apple/container#1703). Any Berthly user who ran container system kernel set --tar <url> pre-1.2.0 has exactly that config shape, and would silently lose their whole system config on next load once the daemon is upgraded.
  • Extracts the load-outcome handling into a pure, testable mapSystemConfigLoadResult(_:) and surfaces a failure through the existing lastStartupWarning mechanism instead of swallowing it.

Why

Found while implementing #79 (kernel digest verification) — this milestone's own version bump is what triggers the failure for affected users, so it needs fixing in the same milestone.

Closes#87

Test plan

  • xcodebuild build succeeds
  • xcodebuild test -only-testing:BerthlyTests — full suite passes, including new SystemConfigLoadResultMappingTests covering both the success and failure paths
  • swiftlint lint --strict — 0 violations
  • No View files touched — no UI test needed for this change (tracked separately in No UI test coverage for the sidebar's daemon warning state #89: the sidebar's warning-state rendering itself has no UI coverage yet, pre-existing gap this PR surfaces a second producer into)

resolvedSystemConfig() used `try?` around ConfigurationLoader.load(),
so any decode failure silently fell back to an all-defaults
ContainerSystemConfig — not just for the field that failed, the user's
entire config.toml (DNS, build settings, kernel, everything).
ConfigurationLoader.load() only throws when a config file exists on
disk but fails to parse/decode; it already returns cleanly with
defaults when no file is present at all. So every error caught here
represents real discarded user data, not an absent-file no-op.
container 1.2.0 made this concrete: KernelConfig's decoder now throws
when kernel.url is customized without a paired kernel.digest
(apple/container#1703) — any Berthly user who ran `container system
kernel set --tar <url>` pre-1.2.0 has exactly that config shape, and
would silently lose their whole system config on next load once the
daemon is upgraded.
Extracts the load-outcome handling into a pure, testable
mapSystemConfigLoadResult(_:) and surfaces a failure through the
existing lastStartupWarning mechanism instead of swallowing it.
@henrywang
henrywang merged commit 0511b2f into mainAug 4, 2026
5 checks passed
@henrywang
henrywang deleted the fix-silent-config-discard branch August 4, 2026 10:22
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.

resolvedSystemConfig() silently discards a user's entire config on a decode failure

1 participant

@henrywang