Successfully recovered 101 missing tabs from Firefox-based browser (Zen Browser) session backups after a browser restart caused tab loss.
Problem: Browser had ~305 tabs before restart, only 207 tabs after restart (101 tabs lost)
Solution: Located and extracted tab data from sessionstore backup files (.jsonlz4 compressed JSON files)
Firefox/Zen browser stores session data in the profile directory:
~/.var/app/app.zen_browser.zen/.zen/<PROFILE_NAME>/
├── sessionstore-backups/
│ ├── previous.jsonlz4 # Previous session (most recent)
│ ├── recovery.jsonlz4 # Current recovery file
│ ├── recovery.baklz4 # Backup of recovery
│ └── upgrade.jsonlz4-* # Session before browser upgrades
├── zen-sessions.jsonlz4 # Zen-specific session data
└── zen-sessions-backup/ # Timestamped Zen session backups
└── zen-sessions-*.jsonlz4
| File | Purpose |
|---|---|
previous.jsonlz4 |
Most important - contains the last session before current one |
recovery.jsonlz4 |
Current session that browser will restore on startup |
upgrade.jsonlz4-* |
Sessions saved before browser upgrades (can be very useful) |
mkdir -p ~/mozrescueThe .jsonlz4 files use Mozilla's LZ4 compression format with a special mozLz40\0 header.
Script: extract_sessions.py
#!/usr/bin/env python3
import lz4.block
import json
import sys
import os
from pathlib import Path
from datetime import datetime
def decompress_jsonlz4(filepath):
"""Decompress a Mozilla jsonlz4 file"""
with open(filepath, 'rb') as f:
magic = f.read(8)
if magic[:8] != b'mozLz40\0':
raise ValueError(f"Invalid magic header: {magic}")
compressed = f.read()
decompressed = lz4.block.decompress(compressed)
return json.loads(decompressed)
def count_tabs(data):
"""Count total tabs across all windows"""
total = 0
if 'windows' in data:
for win in data['windows']:
total += len(win.get('tabs', []))
return total
def extract_urls(data):
"""Extract all URLs from session data"""
urls = []
if 'windows' in data:
for win_idx, win in enumerate(data['windows']):
for tab_idx, tab in enumerate(win.get('tabs', [])):
entries = tab.get('entries', [])
if entries:
# Get the current entry (last visited URL in tab history)
current_idx = tab.get('index', len(entries)) - 1
if 0 <= current_idx < len(entries):
entry = entries[current_idx]
else:
entry = entries[-1]
urls.append({
'window': win_idx,
'tab': tab_idx,
'url': entry.get('url', ''),
'title': entry.get('title', ''),
})
return urls
def main():
profile_dir = Path(os.path.expanduser("~/.mozilla/firefox/PROFILE_NAME"))
output_dir = Path(os.path.expanduser("~/mozrescue"))
# Find all jsonlz4 files
files_to_check = []
# Session store backups
backup_dir = profile_dir / "sessionstore-backups"
if backup_dir.exists():
files_to_check.extend(backup_dir.glob("*.jsonlz4"))
files_to_check.extend(backup_dir.glob("*.baklz4"))
print("=" * 80)
print("SESSION FILE ANALYSIS")
print("=" * 80)
print(f"{'File':<55} {'Tabs':>6} {'Modified':<20}")
print("-" * 80)
results = []
for filepath in sorted(files_to_check):
try:
data = decompress_jsonlz4(filepath)
tab_count = count_tabs(data)
mtime = datetime.fromtimestamp(filepath.stat().st_mtime)
rel_path = str(filepath.relative_to(profile_dir))
results.append({
'path': filepath,
'rel_path': rel_path,
'tabs': tab_count,
'mtime': mtime,
'data': data
})
marker = " <-- TARGET!" if tab_count == 305 else (" <-- CLOSE" if 300 <= tab_count <= 310 else "")
print(f"{rel_path:<55} {tab_count:>6} {mtime.strftime('%Y-%m-%d %H:%M:%S')}{marker}")
except Exception as e:
print(f"{str(filepath):<55} ERROR: {e}")
print("-" * 80)
# Export all sessions with significant tabs
if results:
print(f"\nExporting all sessions to {output_dir}...")
for r in results:
if r['tabs'] > 50: # Only export sessions with substantial tabs
safe_name = r['rel_path'].replace('/', '_').replace('.jsonlz4', '').replace('.baklz4', '')
# Export raw JSON
json_path = output_dir / f"{safe_name}.json"
with open(json_path, 'w') as f:
json.dump(r['data'], f, indent=2)
# Export URL list
urls = extract_urls(r['data'])
url_path = output_dir / f"{safe_name}_urls.txt"
with open(url_path, 'w') as f:
for u in urls:
f.write(f"{u['title']}\n {u['url']}\n\n")
print(f" Exported: {safe_name} ({r['tabs']} tabs)")
print(f"\nDone! Check {output_dir} for extracted data.")
if __name__ == "__main__":
main()Run with:
# Using uvx (recommended - no setup needed)
uvx --with lz4 python ~/mozrescue/extract_sessions.py
# Or with virtual environment
python3 -m venv ~/mozrescue/venv
source ~/mozrescue/venv/bin/activate
pip install lz4
python ~/mozrescue/extract_sessions.pyScript: export_all.py
#!/usr/bin/env python3
import lz4.block
import json
import os
from pathlib import Path
def decompress_jsonlz4(filepath):
with open(filepath, 'rb') as f:
f.read(8) # Skip 'mozLz40\0' header
return json.loads(lz4.block.decompress(f.read()))
def extract_urls(data):
urls = []
if 'windows' in data:
for win_idx, win in enumerate(data['windows']):
for tab_idx, tab in enumerate(win.get('tabs', [])):
entries = tab.get('entries', [])
if entries:
current_idx = tab.get('index', len(entries)) - 1
entry = entries[max(0, min(current_idx, len(entries)-1))]
urls.append({
'window': win_idx,
'tab': tab_idx,
'url': entry.get('url', ''),
'title': entry.get('title', ''),
})
return urls
profile_dir = Path(os.path.expanduser("~/.mozilla/firefox/PROFILE_NAME"))
output_dir = Path(os.path.expanduser("~/mozrescue"))
backup_dir = profile_dir / "sessionstore-backups"
files = [
("previous_session", backup_dir / "previous.jsonlz4"),
("recovery_current", backup_dir / "recovery.jsonlz4"),
]
for name, fpath in files:
if fpath.exists():
data = decompress_jsonlz4(fpath)
urls = extract_urls(data)
# Full JSON
with open(output_dir / f"{name}.json", 'w') as f:
json.dump(data, f, indent=2)
# URL list (readable)
with open(output_dir / f"{name}_urls.txt", 'w') as f:
for i, u in enumerate(urls, 1):
f.write(f"{i:3}. {u['title'][:80]}\n {u['url']}\n\n")
# URL only (for diff/importing)
with open(output_dir / f"{name}_urls_only.txt", 'w') as f:
for u in urls:
f.write(f"{u['url']}\n")
print(f"Exported: {name} ({len(urls)} tabs)")
# Find missing tabs by comparing previous to current
print("\n" + "="*60)
print("MISSING TABS ANALYSIS")
print("="*60)
prev_data = decompress_jsonlz4(backup_dir / "previous.jsonlz4")
curr_data = decompress_jsonlz4(backup_dir / "recovery.jsonlz4")
prev_urls = set(u['url'] for u in extract_urls(prev_data))
curr_urls = set(u['url'] for u in extract_urls(curr_data))
missing = prev_urls - curr_urls
print(f"\nTabs in previous but NOT in current: {len(missing)}")
with open(output_dir / "MISSING_TABS.txt", 'w') as f:
f.write(f"# Missing tabs: {len(missing)}\n")
f.write(f"# These were in previous session but not in current\n\n")
prev_list = extract_urls(prev_data)
for u in prev_list:
if u['url'] in missing:
f.write(f"{u['title']}\n{u['url']}\n\n")
print(f"Saved missing tab list to: {output_dir}/MISSING_TABS.txt")Run with:
uvx --with lz4 python ~/mozrescue/export_all.py# Backup current session first
cp -a ~/.mozilla/firefox/PROFILE_NAME/sessionstore-backups \
~/mozrescue/sessionstore-backups-BACKUP-$(date +%Y%m%d-%H%M%S)
# Replace recovery files with previous session
cp ~/.mozilla/firefox/PROFILE_NAME/sessionstore-backups/previous.jsonlz4 \
~/.mozilla/firefox/PROFILE_NAME/sessionstore-backups/recovery.jsonlz4
cp ~/.mozilla/firefox/PROFILE_NAME/sessionstore-backups/previous.jsonlz4 \
~/.mozilla/firefox/PROFILE_NAME/sessionstore-backups/recovery.baklz4
# Start browser - it should restore the previous sessionUse the generated MISSING_TABS.txt file:
- Manual: Open
~/mozrescue/MISSING_TABS.txtand click/copy URLs - Browser Extension: Use extensions like "Open Multiple URLs" to bulk-open from
*_urls_only.txt - Script: Create a script to open URLs in new tabs
If you want to keep both current and previous tabs:
- Export current session URLs
- Export previous session URLs
- Combine the URL lists
- Use bulk URL opener to restore all tabs
If session files are corrupted, you can recover recently visited URLs from the history database:
sqlite3 ~/.mozilla/firefox/PROFILE_NAME/places.sqlite \
"SELECT url, title, datetime(last_visit_date/1000000, 'unixepoch')
FROM moz_places
ORDER BY last_visit_date DESC
LIMIT 500;" > ~/mozrescue/recent_history.txtls -la ~/.mozilla/firefox/Crash\ Reports/-
Enable Session Restore: Ensure Firefox is set to restore previous session
- Settings → General → Startup → Open previous windows and tabs
-
Use Bookmarks Folder: Bookmark all tabs occasionally
- Right-click tab bar → Bookmark All Tabs
-
Backup Session Files: Periodically copy sessionstore-backups folder
-
Browser Extensions: Consider tab session managers like:
- Tab Session Manager
- Session Buddy
- OneTab
-
Cloud Sync: Enable Firefox Sync to backup tabs to cloud
The file may be corrupted or not a valid jsonlz4 file. Try other backup files.
Install with: pip install lz4 or use uvx --with lz4 python script.py
- Check file permissions
- Verify browser is completely closed before replacing files
- Try copying to
recovery.jsonlz4ANDrecovery.baklz4 - Check browser console for errors (Developer Tools)
Browser might not have updated backups. Check:
- Older upgrade backup files
- History database (places.sqlite)
- System backups/snapshots
After running the scripts, you'll find in ~/mozrescue/:
MISSING_TABS.txt- List of tabs that were lostprevious_session.json- Full session JSONprevious_session_urls.txt- Readable URL list with titlesprevious_session_urls_only.txt- Plain URL list (one per line)recovery_current.json- Current session JSONsessionstore-backups-BACKUP-*- Safety backup of original files
- Header: 8 bytes (
mozLz40\0) - Body: LZ4 compressed JSON data
- Decompression: Use
lz4.block.decompress()after skipping header
{
"windows": [
{
"tabs": [
{
"entries": [
{
"url": "https://example.com",
"title": "Example Page"
}
],
"index": 1
}
]
}
]
}windows[]: Array of browser windowstabs[]: Array of tabs in each windowentries[]: Navigation history for each tabindex: Current position in navigation history (1-based)
Scripts provided as-is for personal use. Modify as needed for your recovery situation.