Skip to content

fix(platform): defer PlatformAPI resolution to prevent Linux segfault#3701

Closed
enXov wants to merge 3 commits intospicetify:mainfrom
enXov:fix/linux-segfault-3695
Closed

fix(platform): defer PlatformAPI resolution to prevent Linux segfault#3701
enXov wants to merge 3 commits intospicetify:mainfrom
enXov:fix/linux-segfault-3695

Conversation

@enXov
Copy link

@enXov enXov commented Feb 17, 2026

Summary

Fixes #3695 — Spotify 1.2.82 on Linux segfaults on launch after spicetify backup apply.

Root Cause

Spicetify eagerly calls all _platform.get*() native getter functions at startup in waitForPlatform(). On Spotify 1.2.82 for Linux, one or more of these native getters triggers a SIGSEGV (segmentation fault) in Chromium's native layer. Since this is a native crash, JavaScript try-catch cannot intercept it.

Fix

Convert all PlatformAPI getter resolution from eager (called at startup) to lazy (Object.defineProperty getters that resolve on first access):

  • Native code is never called at startup — zero getter invocations during init
  • Each API self-caches its resolved value on first access
  • Failed resolutions return undefined instead of crashing
  • Backward-compatible: on Windows/macOS, APIs resolve identically on first access

Changes

jsHelper/spicetifyWrapper.js

  • waitForPlatform(): Replace eager _platform[key]() calls with lazy Object.defineProperty getters
  • addMissingPlatformAPIs(): Replace eager Registry.resolve() calls with lazy getters
  • Add defensive error handling for version parsing and Registry iteration

jsHelper/homeConfig.js

  • Add null check for Spicetify.Platform.History before accessing .location

jsHelper/expFeatures.js

  • Add optional chaining for RemoteConfigDebugAPI?.getProperties()

src/apply/apply.go

  • insertExpFeatures: Wrap expFeatureOverride hook with typeof check + try-catch
  • insertExpFeatures: Wrap RemoteConfigResolver and createInternalMap captures with try-catch
  • insertHomeConfig: Guard SpicetifyHomeConfig.arrange with typeof check + fallback

Testing

  • 15 unit tests added (jsHelper/tests/platformAPI.test.js), all passing
  • Manually verified on Linux with Spotify 1.2.82.428 — Spotify opens, music plays, Marketplace loads, no segfault

Summary by CodeRabbit

  • Tests

    • Added comprehensive tests validating lazy Platform API resolution, caching, error handling, enumeration, and registry behavior.
  • Bug Fixes

    • Strengthened runtime guards and error handling to prevent crashes when Platform pieces are missing.
    • Home initialization now safely skips when required components are absent.
  • New Features

    • Introduced lazy, on-first-access loading and caching for Platform and registry-provided APIs.

…etify#3695)

On Spotify 1.2.82 for Linux, eagerly calling all _platform.get*() native
getters at startup triggers a SIGSEGV in Chromium's native layer. This
change converts all PlatformAPI getters to lazy Object.defineProperty
accessors that only invoke native code on first access.

Changes:
- spicetifyWrapper.js: Lazy getters for waitForPlatform() and
  addMissingPlatformAPIs() via Object.defineProperty with self-caching
- homeConfig.js: Null check for Platform.History
- expFeatures.js: Optional chaining for RemoteConfigDebugAPI
- apply.go: Defensive typeof checks for expFeatureOverride,
  RemoteConfigResolver, and HomeConfig.arrange hooks

Tested on Linux 1.2.82 — Spotify opens, Marketplace loads, no segfault.
Backward-compatible with Windows/macOS (APIs resolve on first access).
@coderabbitai
Copy link

coderabbitai bot commented Feb 17, 2026

No actionable comments were generated in the recent review. 🎉


📝 Walkthrough

Walkthrough

Adds lazy getters to Spicetify.Platform (including Registry-backed entries) that resolve and cache APIs on first access with error handling; adds runtime guards elsewhere to avoid startup native calls; introduces a comprehensive test suite validating lazy resolution, caching, enumeration, and failure modes.

Changes

Cohort / File(s) Summary
Platform wrapper & lazy getters
jsHelper/spicetifyWrapper.js
Replaces eager Platform API assignment with Object.defineProperty lazy getters that call _platform[getter](), cache the resolved value by redefining the property, and catch/log resolution errors. Adds Registry-based lazy resolution via Spicetify.Platform.Registry.resolve() with guards around _map, version parsing, and try/catch around iteration.
Tests for lazy Platform APIs
jsHelper/__tests__/platformAPI.test.js
Adds a comprehensive test suite simulating lazy getters and Registry resolution. Covers first-access resolution, self-caching, failing getters returning undefined, selective invocation, enumeration visibility (Object.keys, Object.hasOwn), Registry behaviors (missing/crashing resolutions, skipping existing APIs), and platform-version-specific flows.
Optional chaining & runtime guards
jsHelper/expFeatures.js, jsHelper/homeConfig.js
expFeatures.js: adds optional chaining to RemoteConfigDebugAPI?.getProperties() and RemoteConfigResolver?.value?.remoteConfiguration. homeConfig.js: early-return guard when Spicetify.Platform.History is absent (logs a warning).
Safe injects in build/apply
src/apply/apply.go
Wraps injected calls with existence checks and try/catch-style guards for insertHomeConfig and insertExpFeatures, replacing direct calls with guarded expressions to avoid runtime errors when APIs are absent.

Sequence Diagram(s)

mermaid
sequenceDiagram
autonumber
participant Client as Client
participant Platform as Spicetify.Platform
participant Native as Native _platform
participant Registry as Spicetify.Platform.Registry
Client->>Platform: access Platform.X
Note right of Platform: lazy getter defined
Platform->>Native: call _platform.getX()
alt native resolves
Native-->>Platform: returns API object
Platform-->>Platform: redefine Platform.X = API (cache)
Platform-->>Client: return API
else native throws
Native-->>Platform: throws/error
Platform-->>Platform: define Platform.X = undefined (cache)
Platform-->>Client: return undefined
end
Client->>Platform: access Registry.Y
Platform->>Registry: Registry.resolve("key")
alt registry resolves
Registry-->>Platform: returns API
Platform-->>Platform: redefine Platform.Y = API (cache)
Platform-->>Client: return API
else registry throws/missing
Registry-->>Platform: throws/undefined
Platform-->>Platform: define Platform.Y = undefined (cache)
Platform-->>Client: return undefined
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nudge a getter, wait to see—

it wakes, returns, then sleeps for me.
Cached and quiet, errors tucked away,
hopping softly through the day. 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: converting eager PlatformAPI resolution to lazy resolution to prevent Linux segfault on Spotify 1.2.82.
Linked Issues check ✅ Passed The PR comprehensively addresses #3695 by converting eager PlatformAPI getter invocation to lazy resolution, adding defensive guards, and including unit tests demonstrating backward compatibility across platforms.
Out of Scope Changes check ✅ Passed All changes are directly related to preventing Linux segfault through lazy API resolution and defensive guards; no unrelated or out-of-scope modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/apply/apply.go (1)

481-486: ⚠️ Potential issue | 🔴 Critical

Fix unbalanced object literal in RemoteConfigResolver try/catch wrapper.

The replacement at line 484 generates invalid JavaScript due to incomplete brace matching. Submatches[4] captures {value:{localConfiguration (two opening braces, zero closing), and the format string ends with %s}catch(_e){};. When combined, this produces:

try{Spicetify.RemoteConfigResolver={value:{localConfiguration}catch(_e){};

The single } only closes {localConfiguration, leaving the outer {value: unclosed before the try block ends, resulting in a parse-time SyntaxError that breaks the entire xpui.js file.

The fix is to add an extra closing brace:

Add closing brace for the outer object
-			return fmt.Sprintf("%stry{Spicetify.createInternalMap=%s}catch(_e){};%stry{Spicetify.RemoteConfigResolver=%s}catch(_e){};{value:{localConfiguration", submatches[1], submatches[2], submatches[3], submatches[4])
+			return fmt.Sprintf("%stry{Spicetify.createInternalMap=%s}catch(_e){};%stry{Spicetify.RemoteConfigResolver=%s}}catch(_e){};{value:{localConfiguration", submatches[1], submatches[2], submatches[3], submatches[4])

This ensures both the assignment object and the try block are properly closed before the catch clause.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apply/apply.go` around lines 481 - 486, The replacement using
utils.ReplaceOnce that builds the fmt.Sprintf for wrapping
Spicetify.RemoteConfigResolver produces an unbalanced object literal because
submatches[4] includes "{value:{localConfiguration" (two opens) but the format
string only supplies one closing brace; update the fmt.Sprintf format used in
the anonymous function inside ReplaceOnce (the code that references
submatches[1..4]) to add one more closing '}' before the "catch(_e){};" so the
assigned object literal is fully closed (i.e., ensure the format string ends
with "...%s}catch(_e){};}" or otherwise places an extra '}' to balance the outer
"{value:{" captured in submatches[4]).
🧹 Nitpick comments (2)
jsHelper/__tests__/platformAPI.test.js (1)

1-30: Custom test harness is pragmatic but worth noting the trade-off.

Since spicetifyWrapper.js relies on browser globals (window, DOM), a Node.js-based test file that simulates the lazy getter logic (rather than importing it) is a reasonable approach. Be aware that simulateWaitForPlatform / simulateAddMissingPlatformAPIs duplicate the production code — if the logic in spicetifyWrapper.js changes, these helpers must be updated in lockstep.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jsHelper/__tests__/platformAPI.test.js` around lines 1 - 30, The test file
duplicates production logic for lazy PlatformAPI resolution
(simulateWaitForPlatform and simulateAddMissingPlatformAPIs) which risks getting
out of sync with spicetifyWrapper.js; instead, refactor the tests to import the
real lazy-getter behavior from spicetifyWrapper.js or centralize the shared
helper into a single test utility used by both code and tests. Update test
harness to call the exported helper (or require a new test-utils module) so
simulateWaitForPlatform and simulateAddMissingPlatformAPIs are removed from
platformAPI.test.js and the test exercises the actual get/set behavior from
spicetifyWrapper.js.
jsHelper/spicetifyWrapper.js (1)

363-401: LGTM — Registry-based lazy getters with proper guards.

The Object.hasOwn check correctly avoids overwriting APIs already resolved from waitForPlatform. The outer try/catch at line 399 handles unexpected iteration failures gracefully.

One thing to note: if Spicetify.Platform.version is unavailable (line 356 fails), the function returns early without setting up Registry APIs. If there's a scenario where Registry is available but version is not (or is malformed), this would silently skip all Registry-based resolution. Consider whether a version parse failure should prevent Registry resolution, or whether they should be independent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jsHelper/spicetifyWrapper.js` around lines 363 - 401, The current flow
bail-outs when Spicetify.Platform.version is missing or malformed which prevents
setting up Registry-based lazy getters; update the logic so
Spicetify.Platform.Registry handling is independent of
Spicetify.Platform.version: remove or change the early return/path that depends
on Spicetify.Platform.version (the code referencing Spicetify.Platform.version
and the conditional that returns before the Registry loop) and instead only skip
the Registry loop when Spicetify.Platform.Registry or
Spicetify.Platform.Registry._map is absent; keep the existing guards around
Spicetify.Platform.Registry and the try/catch in the Registry iteration so
Registry resolution proceeds even if version parsing fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@jsHelper/__tests__/platformAPI.test.js`:
- Around line 145-152: Refactor the inline multi-statement arrow getters on the
_platform object (getPlayerAPI and getHistoryAPI) to match project formatting:
replace the compact inline arrow bodies with properly formatted function bodies
(either a named function expression or an arrow with a block and an explicit
return on its own line), ensuring consistent newlines and indentation so the
simulateWaitForPlatform(_platform) call and exported symbols (Platform,
getterCallLog) remain unchanged; this will satisfy the CI formatter while
preserving the callCount increment and returned objects.

---

Outside diff comments:
In `@src/apply/apply.go`:
- Around line 481-486: The replacement using utils.ReplaceOnce that builds the
fmt.Sprintf for wrapping Spicetify.RemoteConfigResolver produces an unbalanced
object literal because submatches[4] includes "{value:{localConfiguration" (two
opens) but the format string only supplies one closing brace; update the
fmt.Sprintf format used in the anonymous function inside ReplaceOnce (the code
that references submatches[1..4]) to add one more closing '}' before the
"catch(_e){};" so the assigned object literal is fully closed (i.e., ensure the
format string ends with "...%s}catch(_e){};}" or otherwise places an extra '}'
to balance the outer "{value:{" captured in submatches[4]).

---

Nitpick comments:
In `@jsHelper/__tests__/platformAPI.test.js`:
- Around line 1-30: The test file duplicates production logic for lazy
PlatformAPI resolution (simulateWaitForPlatform and
simulateAddMissingPlatformAPIs) which risks getting out of sync with
spicetifyWrapper.js; instead, refactor the tests to import the real lazy-getter
behavior from spicetifyWrapper.js or centralize the shared helper into a single
test utility used by both code and tests. Update test harness to call the
exported helper (or require a new test-utils module) so simulateWaitForPlatform
and simulateAddMissingPlatformAPIs are removed from platformAPI.test.js and the
test exercises the actual get/set behavior from spicetifyWrapper.js.

In `@jsHelper/spicetifyWrapper.js`:
- Around line 363-401: The current flow bail-outs when
Spicetify.Platform.version is missing or malformed which prevents setting up
Registry-based lazy getters; update the logic so Spicetify.Platform.Registry
handling is independent of Spicetify.Platform.version: remove or change the
early return/path that depends on Spicetify.Platform.version (the code
referencing Spicetify.Platform.version and the conditional that returns before
the Registry loop) and instead only skip the Registry loop when
Spicetify.Platform.Registry or Spicetify.Platform.Registry._map is absent; keep
the existing guards around Spicetify.Platform.Registry and the try/catch in the
Registry iteration so Registry resolution proceeds even if version parsing
fails.

- Format test file with biome (multi-line arrow functions)
- Fix unbalanced braces in RemoteConfigResolver try-catch wrapper
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
jsHelper/__tests__/platformAPI.test.js (3)

15-35: Custom test harness is pragmatic but may be worth migrating to an established runner.

The hand-rolled test/describe/report functions work, but they lack features like beforeEach, async support, and IDE integration that a lightweight runner (e.g., node --test built-in, available since Node 18) would provide for free. Not blocking, but worth considering as the test suite grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jsHelper/__tests__/platformAPI.test.js` around lines 15 - 35, Replace the
custom test harness (functions test, describe and the counters
testsPassed/testsFailed and failures) with a real test runner: remove the
hand-rolled test/describe/report implementation and migrate tests to use the
built-in Node test API (import { test, describe, beforeEach } from 'node:test')
or another chosen runner (e.g., Jest); update each test case to use async
functions where needed, replace manual assertions with assert from 'node:assert'
or the runner's expect, and remove reliance on testsPassed/testsFailed/failures
so the runner handles reporting and lifecycle hooks like beforeEach. Ensure any
top-level describe/test calls are converted to the runner's APIs (refer to test
and describe symbols) and that async tests return/promises are awaited.

41-78: Tests exercise duplicated simulation logic rather than the actual spicetifyWrapper.js functions.

simulateWaitForPlatform and simulateAddMissingPlatformAPIs re-implement the lazy-getter logic from spicetifyWrapper.js (lines 316-326, 369-376). If the production code changes, these simulations can silently drift out of sync and give false-passing tests.

Consider extracting the core lazy-getter wiring into importable helper functions in spicetifyWrapper.js (or a shared module) and importing them here, so the tests exercise the real code path.

Also applies to: 80-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jsHelper/__tests__/platformAPI.test.js` around lines 41 - 78, The tests
reimplement the lazy-getter wiring (simulateWaitForPlatform /
simulateAddMissingPlatformAPIs) which can drift from spicetifyWrapper.js;
extract the shared lazy-getter logic into an exported helper function (e.g.,
createLazyGetters or wireLazyGetters) in spicetifyWrapper.js or a small shared
module and update spicetifyWrapper.js to call that helper where it currently
defines the getters; then update the tests to import and call this helper
(preserving the getterCallLog behavior) instead of duplicating the
implementation so tests exercise the real code path.

454-466: Consider printing the report and exiting unconditionally to avoid silent swallowing.

If the test file is run in a CI pipeline, process.exit(1) on failure is correct. However, if someone imports this file as a module (unlikely but possible), the top-level process.exit would terminate the parent process. A minor concern given the standalone design, but worth noting.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@jsHelper/__tests__/platformAPI.test.js` around lines 454 - 466, The test
runner currently prints the summary and calls process.exit(...) unconditionally
which can terminate a parent process if this file is imported; change it so the
report printing always happens but the call to process.exit(testsFailed > 0 ? 1
: 0) is only executed when the file is run directly (use the standard check for
module entry, e.g. require.main === module) or export a run function that
returns the exit code instead of exiting; ensure you reference and use the
existing symbols testsPassed, testsFailed, failures and only guard the
process.exit call while keeping the failure reporting logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/apply/apply.go`:
- Line 485: The formatted JS string building the RemoteConfigResolver try-catch
is missing a closing brace: in the fmt.Sprintf that constructs the snippet
assigning Spicetify.RemoteConfigResolver (the line using submatches[1..4]),
change the sequence right before catch from "}}catch" to "}}}catch" so the
nested object literal and the try block are properly closed (i.e., ensure the
format string around Spicetify.RemoteConfigResolver ends with three closing
braces before "catch(_e){};").

---

Nitpick comments:
In `@jsHelper/__tests__/platformAPI.test.js`:
- Around line 15-35: Replace the custom test harness (functions test, describe
and the counters testsPassed/testsFailed and failures) with a real test runner:
remove the hand-rolled test/describe/report implementation and migrate tests to
use the built-in Node test API (import { test, describe, beforeEach } from
'node:test') or another chosen runner (e.g., Jest); update each test case to use
async functions where needed, replace manual assertions with assert from
'node:assert' or the runner's expect, and remove reliance on
testsPassed/testsFailed/failures so the runner handles reporting and lifecycle
hooks like beforeEach. Ensure any top-level describe/test calls are converted to
the runner's APIs (refer to test and describe symbols) and that async tests
return/promises are awaited.
- Around line 41-78: The tests reimplement the lazy-getter wiring
(simulateWaitForPlatform / simulateAddMissingPlatformAPIs) which can drift from
spicetifyWrapper.js; extract the shared lazy-getter logic into an exported
helper function (e.g., createLazyGetters or wireLazyGetters) in
spicetifyWrapper.js or a small shared module and update spicetifyWrapper.js to
call that helper where it currently defines the getters; then update the tests
to import and call this helper (preserving the getterCallLog behavior) instead
of duplicating the implementation so tests exercise the real code path.
- Around line 454-466: The test runner currently prints the summary and calls
process.exit(...) unconditionally which can terminate a parent process if this
file is imported; change it so the report printing always happens but the call
to process.exit(testsFailed > 0 ? 1 : 0) is only executed when the file is run
directly (use the standard check for module entry, e.g. require.main === module)
or export a run function that returns the exit code instead of exiting; ensure
you reference and use the existing symbols testsPassed, testsFailed, failures
and only guard the process.exit call while keeping the failure reporting logic
intact.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get context around line 485 in src/apply/apply.go
sed -n '475,495p' src/apply/apply.go | cat -n

Repository: spicetify/cli

Length of output: 985


🏁 Script executed:

#!/bin/bash
# Get the full insertExpFeatures function to understand context
rg -A 50 'func insertExpFeatures' src/apply/apply.go | head -100

Repository: spicetify/cli

Length of output: 2152


🏁 Script executed:

#!/bin/bash
# Find the regex pattern used for this replacement
rg -B 5 'RemoteConfigResolver' src/apply/apply.go | grep -A 5 'regexp\|pattern'

Repository: spicetify/cli

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check if there are test files that verify the output format
find . -type f -name '*test*.go' | xargs grep -l 'RemoteConfigResolver' 2>/dev/null

Repository: spicetify/cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for test files related to apply functionality
find . -type f \( -name '*test.go' -o -name '*_test.go' \) | xargs grep -l 'insertExpFeatures\|RemoteConfigResolver' 2>/dev/null

Repository: spicetify/cli

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look for any integration tests or fixtures
find . -type f \( -name '*.js' -o -name '*.json' \) | xargs grep -l 'RemoteConfigResolver' 2>/dev/null | head -10

Repository: spicetify/cli

Length of output: 113


🏁 Script executed:

#!/bin/bash
# Check git history to see if this was recently changed/fixed
git log --oneline -20 -- src/apply/apply.go 2>/dev/null | head -20

Repository: spicetify/cli

Length of output: 132


🏁 Script executed:

#!/bin/bash
# Search for issues or comments about unbalanced braces in the repo
rg -i 'unbalanced|brace' --type=go src/apply/ 2>/dev/null

Repository: spicetify/cli

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Show the actual changes in the commit that fixed unbalanced braces
git show 152d12f -- src/apply/apply.go 2>/dev/null | head -100

Repository: spicetify/cli

Length of output: 3079


🏁 Script executed:

#!/bin/bash
# Show the full diff for that commit
git diff 152d12f~1 152d12f -- src/apply/apply.go 2>/dev/null | grep -A 5 -B 5 'RemoteConfigResolver'

Repository: spicetify/cli

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Check what the code looks like in the helper files that reference RemoteConfigResolver
cat jsHelper/expFeatures.js 2>/dev/null | grep -A 10 -B 10 'RemoteConfigResolver'

Repository: spicetify/cli

Length of output: 2196


🏁 Script executed:

#!/bin/bash
# Check the other helper file
cat jsHelper/spicetifyWrapper.js 2>/dev/null | grep -A 10 -B 10 'RemoteConfigResolver'

Repository: spicetify/cli

Length of output: 481


🏁 Script executed:

#!/bin/bash
# Verify the exact format string and count braces carefully
sed -n '485p' src/apply/apply.go | cat

Repository: spicetify/cli

Length of output: 271


🏁 Script executed:

#!/bin/bash
# Check if there's a test that shows expected output
find . -type f -name '*.go' -exec grep -l 'RemoteConfigResolver.*try\|catch.*RemoteConfigResolver' {} \;

Repository: spicetify/cli

Length of output: 77


🏁 Script executed:

#!/bin/bash
# Look for any related issues or TODOs
rg -i 'todo|fixme|bug|brace' src/apply/apply.go

Repository: spicetify/cli

Length of output: 39


🏁 Script executed:

#!/bin/bash
# Let me manually trace through the exact substitution to be absolutely certain
cat > /tmp/trace.py << 'EOF'
# Simulate the regex and format string substitution

# Example values that would match the regex
group1 = "Y.fromJSON(z);"
group2 = "Y.fromJSON"
group3 = "return someProvider,"
group4 = "{value:{localConfiguration"

# The format string from the code
fmt = "%stry{Spicetify.createInternalMap=%s}catch(_e){};%stry{Spicetify.RemoteConfigResolver=%s}}catch(_e){};{value:{localConfiguration"

# Perform substitution
result = fmt % (group1, group2, group3, group4)
print("Full formatted result:")
print(result)
print("\n" + "="*80 + "\n")

# Focus on the RemoteConfigResolver part
remote_part = f"try{{Spicetify.RemoteConfigResolver={group4}}}catch(_e){{}}"
print("RemoteConfigResolver section:")
print(remote_part)
print("\nBrace analysis:")

opens = 0
closes = 0
for i, char in enumerate(remote_part):
    if char == '{':
        opens += 1
        print(f"Position {i}: '{char}' - Opens {opens} (depth: {opens})")
    elif char == '}':
        closes += 1
        print(f"Position {i}: '{char}' - Closes {closes} (remaining open: {opens - closes})")

print(f"\nTotal: {opens} opening braces, {closes} closing braces")
print(f"Balanced: {opens == closes}")
EOF
python /tmp/trace.py

Repository: spicetify/cli

Length of output: 780


Fix unbalanced braces in the RemoteConfigResolver try-catch wrapper — produces invalid JavaScript syntax.

The format string on line 485 creates a try-catch block around Spicetify.RemoteConfigResolver assignment with an unclosed try{ brace. The current code produces only 2 closing braces (}}) before catch, but 3 are required to properly close the nested object literals and the try block:

// Current (invalid) — try block never closes:
try{Spicetify.RemoteConfigResolver={value:{localConfiguration}}catch(_e){};

// Correct (valid) — 3 closing braces:
try{Spicetify.RemoteConfigResolver={value:{localConfiguration}}}catch(_e){};

Change }}catch to }}}catch on line 485:

-			return fmt.Sprintf("%stry{Spicetify.createInternalMap=%s}catch(_e){};%stry{Spicetify.RemoteConfigResolver=%s}}catch(_e){};{value:{localConfiguration", submatches[1], submatches[2], submatches[3], submatches[4])
+			return fmt.Sprintf("%stry{Spicetify.createInternalMap=%s}catch(_e){};%stry{Spicetify.RemoteConfigResolver=%s}}}catch(_e){};{value:{localConfiguration", submatches[1], submatches[2], submatches[3], submatches[4])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/apply/apply.go` at line 485, The formatted JS string building the
RemoteConfigResolver try-catch is missing a closing brace: in the fmt.Sprintf
that constructs the snippet assigning Spicetify.RemoteConfigResolver (the line
using submatches[1..4]), change the sequence right before catch from "}}catch"
to "}}}catch" so the nested object literal and the try block are properly closed
(i.e., ensure the format string around Spicetify.RemoteConfigResolver ends with
three closing braces before "catch(_e){};").

…teral cannot be wrapped

The regex only captures the START of an object literal ({value:{localConfiguration).
The rest continues in the original Spotify code. Wrapping this in try-catch
with any number of closing braces produces either invalid JS (}}) or truncated
semantics (}}}). Same issue as preprocess.go PlatformAPI capture.

Keep createInternalMap try-catch since it wraps a simple function reference.
@rxri
Copy link
Member

rxri commented Feb 17, 2026

I appreciate the want of the fix and the explanation of the issue, however:

  • This is a total AI slop
  • A lot of extensions WAIT for the stuff to appear in the Spicetify.Platform this only prevents them from working properly and waiting indefinitely

I'm not really happy about it and am not even considering it to be merged

@enXov
Copy link
Author

enXov commented Feb 17, 2026

Thanks for the review @rxri. I understand the concern about extensions, so let me clarify how the lazy getters actually work:

Extensions are NOT affected. When an extension accesses Spicetify.Platform.PlayerAPI, the lazy getter fires immediately, calls the native getter, caches the result, and returns it — all in the same access. From the extension's perspective, the property is there and works normally. There's no waiting or polling needed.

The only difference from the current behavior is when the native call happens:

Before: All ~20 native getters called eagerly at startup → one crashes → whole app segfaults
After: Each native getter called on first access → only the broken one fails → returns undefined → app continues
On Windows/macOS, the behavior is functionally identical — every API resolves on first access. On Linux 1.2.82, without this fix, Spotify crashes immediately after spicetify backup apply. The current workaround on the issue thread (#3695) is disabling home_config and experimental_features, which breaks Marketplace. This fix keeps everything enabled.

Screencast_20260217_235859.webm

However, if you dont like the code you can reject it 😭 ❤️

@rxri
Copy link
Member

rxri commented Feb 17, 2026

What's the point of this if only one getter will always fail? Let's just find the one that doesn't work and not call it at all lol. This is literally over-engineered and I can tell it's AI because they like to do this even for simple issues

@enXov
Copy link
Author

enXov commented Feb 17, 2026

Thanks for the suggestion but the real problem is on the spotify sides; what i meant, the crash happening on the spotify native c++ codes, so the js cannot catch the errors unfortuanly. What is that meaning is need extremely debug, so thats the basic solution. And yeah im using AI XD

but yeah, in general your right, if you want to me contunie i will contunie to debug

@rxri
Copy link
Member

rxri commented Feb 17, 2026

I am aware this is spotify's fault. They broke it. I said since the beginning that this is not our fault and this just proves it further.

so thats the basic solution

It's over engineered for no reason. The AI should not be used for such an issue anyway. The createInternalMap and expFeatureOverride do not exist on newer Spotify versions because there's an actual PlatformAPI for it; RemoteConfigDebugAPI. It's only keep for compatibility. If disabling home config & exp features make spotify to not crash - it's probably something around RemoteConfigDebugAPI & something else

@enXov
Copy link
Author

enXov commented Feb 17, 2026

yes, but if the home config and exp features is disabled then no marketplace and the spicetify without marketplace is nothing(this time include extensions)

so the as i said, binary research is needed, and i skip that one, dont want to spend time on it, and use the working one since i dont need every api on spotify, so you can close the pr ❤️

@enXov enXov closed this Feb 17, 2026
Sign up for free to 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.

After applying spicetify the Spotify segfaults (1.2.82.428)

2 participants

Comments