Skip to content

Fix packaged updater ASAR extraction handoff - #313

Merged
IgorWarzocha merged 3 commits into
mainfrom
fix/updater-asar-handoff
Aug 20, 2026
Merged

Fix packaged updater ASAR extraction handoff#313
IgorWarzocha merged 3 commits into
mainfrom
fix/updater-asar-handoff

Conversation

@IgorWarzocha

Copy link
Copy Markdown
Owner

Summary

  • extract update archives out of process with the OS tar executable so Electron does not swallow app.asar
  • remove only the exact obsolete Linux wrapper that shadows npm/Bun launchers
  • bump launcher publish versions to 0.1.72 and 0.1.72-dev.0
  • add updater archive and legacy-wrapper migration contracts to the umbrella test suite

Root cause

Electron patches Node filesystem handling for paths ending in app.asar. The in-app updater used the JavaScript tar library inside Electron, which extracted the executable but silently omitted the packaged app.asar. Separately, old Linux installs generated ~/.local/bin/howcode as a frozen direct-app wrapper, shadowing bunx and global package launchers.

Validation

  • exact released Linux archive and old tar package extract correctly under stock Node
  • exact archive under Electron + JavaScript tar reproduces { executable: true, asar: false }
  • committed OS-tar helper under Electron produces { executable: true, asar: true }
  • full gate: 47 test files / 114 tests, React Doctor 100
  • launcher dry-runs clean for 0.1.72 and 0.1.72-dev.0

@IgorWarzocha

Copy link
Copy Markdown
Owner Author

@codex please review this PR and give me 10-20 issues if any. Categorize findings as required, recommended, or optional.

@chatgpt-codex-connector

Copy link
Copy Markdown

💡 Codex Review

if (baseline.kind === 'commit') return `commit:${baseline.sha}`
if (baseline.kind === 'last-opened') return `last-opened:${baseline.rev}`
return baseline.kind

P1 Badge [Required] Include branch names in review context IDs

For branch and parent-branch baselines, this fallback uses only the baseline kind and discards branchName. Switching a project's comparison from one named branch to another therefore reuses the same persisted context, hydrates comments and drafts written against the previous diff, and can send those comments to the agent as if they applied to the new branch. Include the normalized branch name in the scope key.


const projectId = getLocalDraftProjectId(threadId.slice('session:'.length))
return projectId ? `project:${projectId}:new-thread` : null

P1 Badge [Required] Use the mode-specific key when mirroring local drafts

When a local:// session draft is mirrored for recovery, it is stored as project:${projectId}:new-thread, but getComposerDraftThreadId only reads project:${projectId}:${composerMode}:new-thread. Consequently, after the local thread disappears or the app reloads, the mirrored prompt and attachments are unreachable from either the code or chat composer and the draft appears lost. Generate the mirror using the same mode-specific key format.


if (baseline.kind === 'head') {
deleteAppPreference(gitDiffBaselineDefaultKey)
return

P2 Badge [Required] Persist the Head diff baseline selection

Selecting the head baseline deletes the preference, but the reader's new missing-value fallback is main-branch. The UI updates optimistically, then reverts to the default branch after settings are reloaded or the app restarts, so users cannot persist Head as their default. Delete the preference for main-branch instead, or store head explicitly.


temporaryPaths.installDir
? rm(temporaryPaths.installDir, { recursive: true, force: true })
: Promise.resolve(),
]).catch(() => undefined)

P2 Badge [Required] Keep failed-install cleanup under the update lock

When installation fails, withUpdateLock releases the cross-process lock before this outer finally removes the shared ${paths.installDir}.partial directory. A waiting launcher or app updater can acquire the lock and begin its retry while the first process is still recursively deleting that same path, causing the retry's staging tree to be removed or corrupted. Complete this cleanup before the locked callback returns, or use a unique per-attempt staging directory.

AGENTS.md reference: src/electron/main/updater/AGENTS.md:L2-L2


if ((recentEvents?.count ?? 0) > 8) {
return json(request, env, { error: 'Too many vote attempts. Try again in a minute.' }, 429)
}
await env.DB.batch([

P2 Badge [Recommended] Make the vote rate-limit decision atomic

With concurrent /vote requests from the same voter, every request can complete the preceding count query while the event count is still below the threshold, then each proceeds into its own batch and records an event. A burst therefore bypasses the intended rate limit and can generate arbitrarily many writes before later requests are blocked. Make the limit check and event insertion one conditional transactional operation.

AGENTS.md reference: workers/polls/AGENTS.md:L3-L3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@IgorWarzocha
IgorWarzocha merged commit de13b3d into main Aug 20, 2026
@IgorWarzocha
IgorWarzocha deleted the fix/updater-asar-handoff branch August 20, 2026 00:17
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.

1 participant