VS Code Codex infinite spinner fix: reset corrupted webview state A developer published a shell script to fix an infinite spinner bug in the VS Code Codex panel caused by corrupted webview state. The script resets Local Storage and Session Storage directories, which resolves a truncated JavaScript bundle error that prevented the chat UI from mounting. The fix was confirmed effective when Codex worked in a clean VS Code profile. | /usr/bin/env bash | | ===================================================================== | | codex fix.sh | | | | Diagnose and reset the VS Code webview state used by the Codex panel | | from the openai.chatgpt extension. | | | | Symptom: | | Codex works in the terminal, but the VS Code Codex panel stays on an | | infinite spinner and the chat UI never appears. | | | | Confirmed root cause, 2026-07-04: | | Corrupted VS Code webview state in: | | | | ~/.config/Code/Local Storage | | ~/.config/Code/Session Storage | | | | The VS Code webview service worker served a truncated JavaScript bundle | | for the Codex panel. The file on disk was valid, but the webview received | | a broken version and failed with: | | | | Uncaught SyntaxError: Unexpected end of input | | | | Result: the Codex frontend did not mount, so the panel stayed on the | | spinner. | | | | Important diagnostic: | | Run VS Code with a clean profile: | | | | code --user-data-dir=/tmp/vscode-clean | | | | If Codex works there, the problem is not the Codex CLI, account, auth, | | network, extension binary, or model list. It is local VS Code profile | | state. | | | | What this script does: | | - checks that VS Code is closed; | | - finds the openai.chatgpt extension; | | - checks webview JavaScript assets; | | - deletes regenerable webview cache directories; | | - moves Local Storage and Session Storage to .bak; | | - provides a check mode after reopening VS Code. | | | | Usage: | | 1. Close VS Code completely. | | 2. Run: | | | | bash ~/codex fix.sh | | | | 3. Open VS Code and open the Codex panel. | | 4. Run: | | | | bash ~/codex fix.sh check | | | | Notes: | | - Cache directories are deleted because VS Code regenerates them. | | - State directories are moved to .bak so rollback is possible. | | ===================================================================== | | | | set -uo pipefail | | | | CFG="$HOME/.config/Code" | | EXT="$ ls -dt "$HOME"/.vscode/extensions/openai.chatgpt- 2 /dev/null | head -1 " | | | | Regenerable cache directories. These are removed. | | CACHE DIRS= | | "Service Worker" | | "CachedData" | | "Code Cache" | | "GPUCache" | | "WebStorage" | | | | | | Webview state directories. These are moved to .bak. | | STATE DIRS= | | "Local Storage" | | "Session Storage" | | | | | | Asset name fragment from the observed webview console error. | | ASSET HINT="thread-side-panel-tabs" | | | | print section { | | printf '\n== %s ==\n' "$1" | | } | | | | info { | | printf 'INFO: %s\n' "$1" | | } | | | | ok { | | printf 'OK: %s\n' "$1" | | } | | | | warn { | | printf 'WARN: %s\n' "$1" | | } | | | | error { | | printf 'ERROR: %s\n' "$1" | | } | | | | newest codex log { | | local log root | | log root="$ ls -dt "$CFG"/logs/ / 2 /dev/null | head -1 " | | echo "${log root}window1/exthost/openai.chatgpt/Codex.log" | | } | | | | check mode { | | print section "CHECK: state after restarting VS Code" | | | | if pgrep -x code /dev/null; then | | ok "VS Code is running. This is expected in check mode." | | else | | warn "VS Code is not running. Open VS Code and the Codex panel, then run check again." | | fi | | | | print section "Latest Codex extension log" | | local log file | | log file="$ newest codex log " | | | | if -f "$log file" ; then | | info "Log file: $log file" | | echo "-----" | | tail -25 "$log file" | sed 's/^/ /' | | echo "-----" | | | | if grep -q "Initialize received" "$log file"; then | | ok "The app-server initialized." | | else | | warn "No initialization marker found in the latest log." | | fi | | else | | error "Codex.log was not found. The extension may not have activated." | | fi | | | | print section "App-server process" | | if pgrep -af "openai.chatgpt. app-server" /dev/null; then | | ok "App-server process found:" | | pgrep -af "openai.chatgpt. app-server" | sed 's/^/ /' | | else | | warn "No app-server process found." | | fi | | | | print section "Webview cache directories" | | for dir name in "${CACHE DIRS @ }"; do | | if -e "$CFG/$dir name" ; then | | du -sh "$CFG/$dir name" 2 /dev/null | sed 's/^/ /' | | else | | echo " missing: $dir name" | | fi | | done | | | | print section "Result interpretation" | | echo "Logs only show the backend side. The backend may be healthy while the" | | echo "webview frontend is still broken." | | echo | | echo "To verify the actual panel:" | | echo " 1. Open the Codex panel." | | echo " 2. Run: Ctrl+Shift+P" | | echo " 3. Select: Developer: Open Webview Developer Tools" | | echo " 4. Open the Console tab." | | echo | | echo "Look for:" | | echo " ${ASSET HINT}- .js Uncaught SyntaxError: Unexpected end of input" | | echo | | echo "If the error is gone and the chat is visible, the fix worked." | | echo "If the error remains, try installing another extension version:" | | echo " Extensions - Codex/OpenAI ChatGPT - Gear - Install Another Version" | | echo | | echo "Then consider disabling extension auto-update:" | | echo ' "extensions.autoUpdate": false' | | } | | | | main mode { | | print section "1. Is VS Code closed?" | | | | if pgrep -x code /dev/null; then | | error "VS Code is still running. PIDs: $ pgrep -x code | tr '\n' ' ' " | | echo "Cleaning webview state while VS Code is running is unreliable." | | echo "Close VS Code completely and run this script again." | | read -rp "Continue anyway? y/N " answer | | case "${answer:-}" in | | y|Y|yes|YES | | warn "Continuing while VS Code appears to be running." | | ;; | | | | echo "Aborted." | | exit 1 | | ;; | | esac | | else | | ok "VS Code is closed." | | fi | | | | print section "2. Codex extension on disk" | | | | if -z "$EXT" ; then | | error "openai.chatgpt extension was not found in ~/.vscode/extensions" | | else | | ok "Extension directory: $EXT" | | | | if -f "$EXT/package.json" ; then | | local version line | | version line="$ grep -m1 '"version"' "$EXT/package.json" | tr -d ' ",' " | | info "$version line" | | else | | warn "package.json was not found in the extension directory." | | fi | | fi | | | | print section "3. Webview JavaScript asset check" | | | | if -n "$EXT" && -d "$EXT/webview/assets" ; then | | local suspicious | | local total | | suspicious=0 | | total=0 | | | | for file path in "$EXT"/webview/assets/ .js; do | | -e "$file path" || continue | | total=$ total + 1 | | | | local tail text | | tail text="$ tail -c 40 "$file path" | tr -d '\n' " | | | | A healthy bundle usually ends with a JavaScript terminator or | | sourceMappingURL. This is only a weak heuristic. | | if printf '%s' "$tail text" | grep -qE ' }; |sourceMappingURL'; then | | warn "Suspicious ending: $ basename "$file path" $ stat -c%s "$file path" bytes " | | echo " tail: ${tail text: -20}" | | suspicious=$ suspicious + 1 | | fi | | done | | | | info "Checked JS files: $total" | | info "Suspicious JS files: $suspicious" | | | | for file path in "$EXT"/webview/assets/${ASSET HINT}- .js; do | | -e "$file path" || continue | | | | local size | | local parse status | | size="$ stat -c%s "$file path" " | | | | if command -v node /dev/null 2 &1; then | | if node --input-type=module --check < "$file path" /dev/null 2 &1; then | | parse status="parse OK" | | else | | parse status="PARSE FAIL" | | fi | | else | | parse status="node not found, parse check skipped" | | fi | | | | info "$ basename "$file path" : ${size} bytes, ${parse status}" | | done | | else | | warn "Webview assets directory not found." | | fi | | | | print section "4. Webview layers before cleanup" | | | | for dir name in "${CACHE DIRS @ }" "${STATE DIRS @ }"; do | | if -e "$CFG/$dir name" ; then | | du -sh "$CFG/$dir name" 2 /dev/null | sed 's/^/ /' | | else | | echo " missing: $dir name" | | fi | | done | | | | print section "5a. Deleting regenerable cache directories" | | | | for dir name in "${CACHE DIRS @ }"; do | | if -e "$CFG/$dir name" ; then | | if rm -rf "$CFG/$dir name"; then | | ok "Deleted: $dir name" | | else | | error "Failed to delete: $dir name" | | fi | | else | | info "Already missing: $dir name" | | fi | | done | | | | print section "5b. Moving webview state directories to .bak" | | | | for dir name in "${STATE DIRS @ }"; do | | if -e "$CFG/$dir name" ; then | | rm -rf "$CFG/$dir name.bak" 2 /dev/null | | | | if mv "$CFG/$dir name" "$CFG/$dir name.bak"; then | | ok "Moved to .bak: $dir name" | | else | | error "Failed to move: $dir name" | | fi | | else | | info "Already missing: $dir name" | | fi | | done | | | | print section "6. Webview layers after cleanup" | | | | local clean | | clean=1 | | | | for dir name in "${CACHE DIRS @ }" "${STATE DIRS @ }"; do | | if -e "$CFG/$dir name" ; then | | error "Still exists: $dir name" | | clean=0 | | else | | ok "Missing as expected: $dir name" | | fi | | done | | | | print section "DONE" | | | | if "$clean" -eq 1 ; then | | ok "All target layers were cleaned." | | else | | warn "Some target layers still exist. VS Code may have kept files open." | | fi | | | | echo | | echo "Next steps:" | | echo " 1. Open VS Code." | | echo " 2. Open the Codex panel and wait 10 to 15 seconds." | | echo " 3. Run:" | | echo | | echo " bash ~/codex fix.sh check" | | echo | | echo "If the panel works, you may remove backups:" | | echo | | echo " rm -rf ~/.config/Code/ .bak" | | echo | | echo "If the cleanup made things worse, restore state directories:" | | | | for dir name in "${STATE DIRS @ }"; do | | echo " rm -rf ~/.config/Code/\"$dir name\" && mv ~/.config/Code/\"$dir name.bak\" ~/.config/Code/\"$dir name\"" | | done | | } | | | | case "${1:-}" in | | check | | check mode | | ;; | | "" | | main mode | | ;; | | | | error "Unknown argument: $1" | | echo "Usage:" | | echo " bash ~/codex fix.sh" | | echo " bash ~/codex fix.sh check" | | exit 2 | | ;; | | esac |