|
#!/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 |