Skip to content

Instantly share code, notes, and snippets.

@ewired
Created February 3, 2026 05:44
Show Gist options
  • Select an option

  • Save ewired/c9fa69df95c40d8e6275744b0bdd795f to your computer and use it in GitHub Desktop.

Select an option

Save ewired/c9fa69df95c40d8e6275744b0bdd795f to your computer and use it in GitHub Desktop.

Firefox/Zen Browser Tab Recovery Guide

Summary

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)


Session File Locations

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

Key Files for Recovery

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)

Recovery Process

Step 1: Create Recovery Directory

mkdir -p ~/mozrescue

Step 2: Extract and Analyze Sessions

The .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.py

Step 3: Export All Sessions and Find Missing Tabs

Script: 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

Recovery Options

Option 1: Restore Full Previous Session (Automated)

⚠️ Close browser before attempting this!

# 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 session

Option 2: Manually Reopen Missing Tabs

Use the generated MISSING_TABS.txt file:

  1. Manual: Open ~/mozrescue/MISSING_TABS.txt and click/copy URLs
  2. Browser Extension: Use extensions like "Open Multiple URLs" to bulk-open from *_urls_only.txt
  3. Script: Create a script to open URLs in new tabs

Option 3: Merge Sessions

If you want to keep both current and previous tabs:

  1. Export current session URLs
  2. Export previous session URLs
  3. Combine the URL lists
  4. Use bulk URL opener to restore all tabs

Additional Recovery Sources

places.sqlite (Browsing History)

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.txt

Check for Crash Reports

ls -la ~/.mozilla/firefox/Crash\ Reports/

Prevention Tips

  1. Enable Session Restore: Ensure Firefox is set to restore previous session

    • Settings → General → Startup → Open previous windows and tabs
  2. Use Bookmarks Folder: Bookmark all tabs occasionally

    • Right-click tab bar → Bookmark All Tabs
  3. Backup Session Files: Periodically copy sessionstore-backups folder

  4. Browser Extensions: Consider tab session managers like:

    • Tab Session Manager
    • Session Buddy
    • OneTab
  5. Cloud Sync: Enable Firefox Sync to backup tabs to cloud


Troubleshooting

"Invalid magic header" Error

The file may be corrupted or not a valid jsonlz4 file. Try other backup files.

Python lz4 Module Not Found

Install with: pip install lz4 or use uvx --with lz4 python script.py

Session Restore Doesn't Work

  • Check file permissions
  • Verify browser is completely closed before replacing files
  • Try copying to recovery.jsonlz4 AND recovery.baklz4
  • Check browser console for errors (Developer Tools)

All Backup Files Have Same Tab Count

Browser might not have updated backups. Check:

  • Older upgrade backup files
  • History database (places.sqlite)
  • System backups/snapshots

Files Generated

After running the scripts, you'll find in ~/mozrescue/:

  • MISSING_TABS.txt - List of tabs that were lost
  • previous_session.json - Full session JSON
  • previous_session_urls.txt - Readable URL list with titles
  • previous_session_urls_only.txt - Plain URL list (one per line)
  • recovery_current.json - Current session JSON
  • sessionstore-backups-BACKUP-* - Safety backup of original files

Technical Details

jsonlz4 File Format

  • Header: 8 bytes (mozLz40\0)
  • Body: LZ4 compressed JSON data
  • Decompression: Use lz4.block.decompress() after skipping header

Session JSON Structure

{
  "windows": [
    {
      "tabs": [
        {
          "entries": [
            {
              "url": "https://example.com",
              "title": "Example Page"
            }
          ],
          "index": 1
        }
      ]
    }
  ]
}
  • windows[]: Array of browser windows
  • tabs[]: Array of tabs in each window
  • entries[]: Navigation history for each tab
  • index: Current position in navigation history (1-based)

License

Scripts provided as-is for personal use. Modify as needed for your recovery situation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment