Skip to content

Fix workspace creation snapshot crash from custom shortcut - #2170

Closed
lawrencecchen wants to merge 2 commits into
mainfrom
feat-workspace-creation-snapshot-crash
Closed

lawrencecchen wants to merge 2 commits into
mainfrom
feat-workspace-creation-snapshot-crash

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression test that injects a pointer-backed inherited Ghostty config during workspace creation
  • snapshot only the inherited font size and rebuild a clean Ghostty config before creating the new workspace

Testing

  • CMUX_SKIP_ZIG_BUILD=1 ./scripts/reload.sh --tag feat-workspace-creation-snapshot-crash (BUILD SUCCEEDED for the tagged app bundle; the script still exits later in the standalone Zig cmuxd step on this macOS 26 host)
  • CMUX_SKIP_ZIG_BUILD=1 xcodebuild -project GhosttyTabs.xcodeproj -scheme cmux-unit -destination 'platform=macOS' -derivedDataPath /tmp/cmux-feat-workspace-creation-snapshot-crash-test-skipzig -only-testing:cmuxTests/WorkspaceCreationConfigSanitizationTests/testAddWorkspacePassesSanitizedInheritedConfigTemplate test (hosted cmux DEV crashed during startup before the test connected, so this remains an existing test-harness blocker)

Issues

  • Related: crash report from 2026-03-25 on cmux NIGHTLY 0.62.2, TabManager.workspaceCreationSnapshot() EXC_BAD_ACCESS while handling the custom add-workspace shortcut

Summary by cubic

Fixes a crash when creating a workspace via a custom shortcut by sanitizing the inherited terminal config. We now only carry the font size and rebuild a fresh ghostty_surface_config_s to avoid dangling pointers.

  • Bug Fixes
    • Snapshot only the terminal font size and build a clean ghostty_surface_config_s for new workspaces, preventing EXC_BAD_ACCESS from pointer-backed fields.
    • Added a regression test to verify only font_size is inherited and all pointer fields are nil/zero.

Written for commit cb48f5a. Summary will update on new commits.

Summary by CodeRabbit

  • Improvements

    • Enhanced terminal configuration inheritance when creating new workspaces with more reliable font size handling.
  • Tests

    • Added tests to validate workspace configuration sanitization during creation.

@vercel

vercel Bot commented Mar 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 25, 2026 10:26pm

@coderabbitai

coderabbitai Bot commented Mar 25, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Refactored workspace creation to propagate only inherited font size (inheritedTerminalFontPoints) instead of snapshotting the entire config object. Added helper methods to validate font values and rebuild inherited terminal configuration during workspace addition.

Changes

Cohort / File(s) Summary
Config Inheritance Refactoring
Sources/TabManager.swift
Changed WorkspaceCreationSnapshot to store inheritedTerminalFontPoints: Float? instead of full config. Added workspaceCreationConfigTemplate(inheritedTerminalFontPoints:) to rebuild config with validated font size. Exposed inheritedTerminalConfigForNewWorkspace(workspace:) from private to internal. Added inheritedTerminalFontPointsForNewWorkspace(workspace:) helper to extract and validate font size from inherited config.
Config Sanitization Tests
cmuxTests/WorkspaceUnitTests.swift
Added WorkspaceCreationConfigSanitizationTests with custom TabManager subclass to verify config template sanitization: preserves font_size, nullifies working_directory, command, env_vars, and zeroes env_var_count. Includes manual unsafe memory management for C-string allocations and env-var buffer deallocation in test cleanup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A sprightly refactor hops through time,
Config inheritance, now sublime!
Font points dance where snapshots once sprawled,
Sanitized and safe—the workspace called! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main fix: addressing a workspace creation snapshot crash triggered by custom shortcuts.
Description check ✅ Passed The description covers the Summary and Testing sections with technical details. The Checklist has incomplete items. Demo Video, Review Trigger, and full checklist completion are missing or incomplete.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-workspace-creation-snapshot-crash

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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No issues found across 2 files

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmuxTests/WorkspaceUnitTests.swift`:
- Around line 470-500: The test keeps C buffers alive until deinit which doesn't
exercise the snapshot lifetime; after the snapshot is captured in
didCaptureWorkspaceCreationSnapshot(), immediately invalidate the injected C
config created by installInjectedConfig by freeing all retainedCStringPointers
(free each pointer), deinitializing and deallocating retainedEnvVars, and
zeroing or clearing injectedConfig (and set retainedCStringPointers and
retainedEnvVars to nil/empty) so makeWorkspaceForCreation must not rely on the
original buffers remaining valid.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7ee3156f-6be8-4e0b-adc6-0af587494c0e

📥 Commits

Reviewing files that changed from the base of the PR and between 99ca3c9 and cb48f5a.

📒 Files selected for processing (2)
  • Sources/TabManager.swift
  • cmuxTests/WorkspaceUnitTests.swift

Comment on lines +470 to +500
deinit {
retainedEnvVars?.deinitialize(count: 1)
retainedEnvVars?.deallocate()
for pointer in retainedCStringPointers {
free(pointer)
}
}

func installInjectedConfig(fontSize: Float) {
let workingDirectory = strdup("/tmp/cmux-workspace-snapshot")
let command = strdup("echo snapshot")
let envKey = strdup("CMUX_INHERITED_ENV")
let envValue = strdup("1")
let envVars = UnsafeMutablePointer<ghostty_env_var_s>.allocate(capacity: 1)
envVars.initialize(
to: ghostty_env_var_s(
key: UnsafePointer(envKey),
value: UnsafePointer(envValue)
)
)

retainedCStringPointers = [workingDirectory, command, envKey, envValue].compactMap { $0 }
retainedEnvVars = envVars

var config = ghostty_surface_config_new()
config.font_size = fontSize
config.working_directory = UnsafePointer(workingDirectory)
config.command = UnsafePointer(command)
config.env_vars = envVars
config.env_var_count = 1
injectedConfig = config

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Invalidate the injected C config immediately after snapshot capture.

This regression is aimed at a snapshot-lifetime crash, but the test keeps working_directory / command / env_vars alive until deinit. As written, it only proves makeWorkspaceForCreation receives a sanitized template; it can still pass if workspaceCreationSnapshot() regresses to retaining the raw ghostty_surface_config_s and sanitizes later. Freeing or clearing the injected buffers in didCaptureWorkspaceCreationSnapshot() would exercise the actual lifetime boundary that caused the crash.

🧪 Tighten the regression
 private final class UnsafeConfigSnapshotTabManager: TabManager {
     private var retainedCStringPointers: [UnsafeMutablePointer<CChar>] = []
     private var retainedEnvVars: UnsafeMutablePointer<ghostty_env_var_s>?
     private var injectedConfig: ghostty_surface_config_s?
     var capturedConfigTemplate: ghostty_surface_config_s?

+    private func releaseInjectedConfig() {
+        injectedConfig = nil
+        retainedEnvVars?.deinitialize(count: 1)
+        retainedEnvVars?.deallocate()
+        retainedEnvVars = nil
+        for pointer in retainedCStringPointers {
+            free(pointer)
+        }
+        retainedCStringPointers.removeAll()
+    }
+
     deinit {
-        retainedEnvVars?.deinitialize(count: 1)
-        retainedEnvVars?.deallocate()
-        for pointer in retainedCStringPointers {
-            free(pointer)
-        }
+        releaseInjectedConfig()
     }
+
+    override func didCaptureWorkspaceCreationSnapshot() {
+        releaseInjectedConfig()
+    }

Also applies to: 529-545

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

In `@cmuxTests/WorkspaceUnitTests.swift` around lines 470 - 500, The test keeps C
buffers alive until deinit which doesn't exercise the snapshot lifetime; after
the snapshot is captured in didCaptureWorkspaceCreationSnapshot(), immediately
invalidate the injected C config created by installInjectedConfig by freeing all
retainedCStringPointers (free each pointer), deinitializing and deallocating
retainedEnvVars, and zeroing or clearing injectedConfig (and set
retainedCStringPointers and retainedEnvVars to nil/empty) so
makeWorkspaceForCreation must not rely on the original buffers remaining valid.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

1 active deployment
Preview — cb48f5ac Deployed Mar 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants