When AppImages Fail You: Building a Robust Extraction Tool (and Why Cursor Drove Me to It)
Let me start this with a confession:
I really wanted to like AppImages
The promise is compelling: universal Linux binaries that run everywhere without installation. Just download, chmod +x
, and go. It’s the kind of elegant simplicity that makes you think “why didn’t we do this sooner?”
Then reality hits.
The Cursor Catalyst
This whole adventure started when I decided to try Cursor, the AI-powered code editor that’s my daily code editor. Unfortunately, it’s not updated frequently in AUR, so I decided to try the AppImage. When I tried to run it, I got the following error:
$ ./Cursor-1.4.3.AppImage
zsh: no such file or directory: ./Cursor-1.4.3.AppImage
After some experimentation, I got it to run but I now started seeing the following error:
mise ERROR Cursor-1.4.3-x86_64_5054c3a796764b4195108ade2714e281.AppImage is not a valid shim. This likely means you uninstalled a tool and the shim does not point to anything. Run `mise use <TOOL>` to reinstall the tool.
mise ERROR Run with --verbose or MISE_VERBOSE=1 for more information
Regardless, this was a nogo for me, because mise has replaced direnv for me and I use it everywhere. Perplexity informed me:
Mise can be run in many environments, but there are common issues when using mise within AppImages (such as when using the Cursor editor as an AppImage), especially with advanced shell integrations and shims. The error message you are seeing—“AppImage is not a valid shim. This likely means you uninstalled a tool and the shim does not point to anything”—is a known problem occurring specifically in AppImage-packed shells and terminals, often with Zsh.
The AppImage Reality Check
Don’t get me wrong — AppImages solve real problems. Before them, we had:
- RPM/DEB Hell: Dependency conflicts, version mismatches, and the joy of maintaining packages across different distributions
- Snap Confinement: Sandboxing that sometimes broke legitimate app functionality
- Flatpak Complexity: Great technology, but the runtime management can get overwhelming
AppImages promised to be different. Just a filesystem bundle with everything included. But in practice, they still have dependencies — on FUSE, on specific kernel features, on runtime libraries that may or may not be present.
I started thinking about this differently. What if instead of trying to fix the technical issues, I just extracted everything and ran it natively?
Building an Extraction-First Manager
The core idea was simple: instead of running AppImages, extract them and create proper desktop entries that point to the extracted executables. This completely sidesteps the FUSE requirement and gives us some nice benefits:
The Extraction Chain
The first challenge was that extraction isn’t always straightforward. Some AppImages work fine with the built-in --appimage-extract
, but others don’t. I ended up building a chain of fallback methods:
# Method 1: Native AppImage extraction
if ( cd "$tmpdir" && "$appimage" --appimage-extract > /dev/null 2>&1 ); then
printf "%s/squashfs-root" "$tmpdir"
return 0
fi
# Method 2: Direct SquashFS with offset detection
if has_cmd unsquashfs; then
offset=$(LC_ALL=C grep -aob -- 'hsqs' "$appimage" | head -n1 | cut -d: -f1 || true)
if [[ -n "${offset:-}" ]]; then
if ( cd "$tmpdir" && unsquashfs -o "$offset" -d squashfs-root "$appimage" > /dev/null 2>&1 ); then
printf "%s/squashfs-root" "$tmpdir"
return 0
fi
fi
fi
# Method 3: Enhanced binwalk analysis
if has_cmd binwalk && has_cmd unsquashfs; then
# ... more sophisticated offset detection
fi
This approach handles even the most stubborn AppImages. I’ve tested it on everything from simple utilities to complex applications like Cursor, and it consistently works where the standard AppImage runtime fails.
The Idempotency Problem
Here’s something that annoyed me: downloading Cursor-1.4.3.AppImage
and later Cursor-1.5.0.AppImage
would create two separate entries in my Applications folder. That’s not how updates should work.
The solution was to use the application’s internal name instead of the filename. Every AppImage contains a .desktop
file that specifies the real application name. My script extracts this and uses it as the canonical identifier:
read_internal_desktop() {
local root="$1"
local d
d=$(find "$root" -maxdepth 2 -type f -name '*.desktop' | head -n 1 || true)
[[ -n "${d:-}" ]] && printf "%s" "$d"
}
parse_desktop_key() {
local df="$1" key="$2"
awk -F= -v key="$key" 'tolower($1)==tolower(key){print $2; exit}' "$df"
}
Now when I install a newer version of Cursor, it updates the existing installation instead of creating a duplicate. Much better.
File Structure and Integration
The end result is a clean, organized structure:
~/Applications/cursor/ # Based on internal app name
├── cursor.AppImage # Original AppImage file
├── extracted/ # Extracted contents
│ ├── AppRun # Main executable
│ ├── cursor.desktop # Internal desktop file
│ ├── usr/ # Application files
│ └── ...
├── icon # Extracted icon
└── .appimage-manager-meta # Manager metadata
~/.local/share/applications/cursor.desktop # System desktop entry
~/.local/share/icons/hicolor/256x256/apps/cursor.png # System icon
The desktop entry points directly to the extracted AppRun
executable, completely bypassing any AppImage runtime requirements. KDE and GNOME see it as a normal application, complete with proper icons and metadata.
The Developer Experience
Using the tool is straightforward:
# Install or update
./appimage-manager.sh install ./Cursor-1.4.3.AppImage
# Later, updating to a newer version
./appimage-manager.sh install ./Cursor-1.5.0.AppImage # Updates existing install
# List what's installed
./appimage-manager.sh list
# Clean removal
./appimage-manager.sh uninstall cursor
The script handles all the complexity: extraction, icon processing, desktop file creation, and menu integration. And since it’s extraction-based, everything starts faster than traditional AppImages.
Why This Matters
This isn’t just about avoiding FUSE dependencies (though that’s nice). It’s about reliability. AppImages are supposed to be the “just works” solution for Linux software distribution, but too often they don’t. By extracting first and running natively, we get:
- No Runtime Dependencies: No FUSE, no AppImage runtime, no mysterious failures
- Faster Startup: No filesystem mounting overhead
- Better Integration: Standard desktop entries that every DE understands
- Cleaner Updates: Idempotent installs based on actual app names
- Troubleshooting: When something breaks, you can inspect the extracted files directly
The Irony
The funny thing? By avoiding the AppImage runtime entirely, I ended up with a more reliable way to use AppImages. They still serve their purpose as distribution bundles — I just treat them as fancy zip files instead of executable containers.
Open Source and Available
I’ve released the AppImage Extract Installer under Apache 2.0. It’s a single bash script with no dependencies beyond standard Linux utilities (though squashfs-tools
and binwalk
enable the enhanced extraction features).
If you’re dealing with AppImage frustrations, give it a try. And if you find bugs or have suggestions, the issue tracker is open.
Sometimes the best way to fix a technology is to work around it entirely.
Have your own AppImage horror stories? Found a better solution? Hit me up on GitHub or wherever you found this post. I’m always interested in hearing how others solve these kinds of practical problems.