Troubleshooting
Common issues and their solutions when developing Gaivota plugins.
Plugin Not Appearing
Symptoms
- Plugin file exists in
~/.gaivota/plugins/but doesn't show in Settings - No installation prompt appears
Solutions
1. Check file location
# Verify the file exists
ls -la ~/.gaivota/plugins/
# Should show your plugin file
# -rw-r--r-- 1 user staff 1234 Jan 1 12:00 my-plugin.lua
2. Check file extension
- Must be
.lua(not.txt,.lua.bak, etc.) - Case-sensitive on some systems
3. Check for syntax errors
Test your Lua syntax:
# If you have Lua installed
lua -c ~/.gaivota/plugins/my-plugin.lua
Common syntax errors:
-- Missing comma in table
metadata = {
name = "Test" -- Missing comma here!
description = "Test plugin"
}
-- Correct:
metadata = {
name = "Test",
description = "Test plugin"
}
4. Check required components
Your plugin must have:
-- 1. metadata table (required)
metadata = {
name = "...",
description = "...",
version = "1.0",
creates = {
virtual_folder = { ... }
}
}
-- 2. filter function (required)
function filter(email, user_email)
return true
end
-- 3. render function (required)
function render(email, metadata_list)
return { subject = "...", body_html = "..." }
end
5. Restart Gaivota
- Close and reopen the app
- Or trigger a manual scan from Settings > Plugins
Virtual Folder Empty
Symptoms
- Plugin installed successfully
- Virtual folder shows in sidebar
- But "0 items" or "No items found"
Solutions
1. Check filter logic
Add debug prints to see what's happening:
function filter(email, user_email)
print("=== Checking email ===")
print("Subject: " .. (email:subject() or "nil"))
print("From: " .. email:sender())
print("To self: " .. tostring(email:to_self(user_email)))
print("Has links: " .. tostring(email:has_links()))
local result = email:from_self(user_email)
and email:to_self(user_email)
and email:has_links()
print("Result: " .. tostring(result))
return result
end
Check the logs in the Gaivota console or log file.
2. Verify email criteria
- Is the filter too restrictive?
- Are there actually emails matching your criteria?
- Try a simpler filter first:
return true(matches all)
3. Check has_links() behavior
has_links() only returns true if URLs were extracted from message bodies. Gaivota will:
- Run a background refresh when a virtual folder is created
- Process new emails as they arrive
- Allow manual refresh via the folder’s Refresh button
If your folder is still empty, click Refresh and open a few emails to ensure message bodies are cached for extraction.
4. Check email sync status
- Ensure emails have been synced
- Try the "Refresh" button on the folder
Render Not Displaying
Symptoms
- Filter works (items show in list)
- But clicking an item shows blank or broken content
Solutions
1. Check return table fields
function render(email, metadata_list)
-- All fields except body_html are optional but recommended
return {
subject = "My Subject", -- Required for list view
sender = "Sender Name", -- Optional
preview = "Preview text...", -- Optional
body_html = "<p>Content</p>" -- Required for detail view
}
end
2. Check HTML validity
-- Bad: Unclosed tags
local html = "<div><p>Content"
-- Good: Properly closed
local html = "<div><p>Content</p></div>"
3. Check for nil concatenation
-- Error: Concatenating nil
local html = "<p>" .. email:subject() .. "</p>" -- Crashes if subject is nil
-- Safe: Handle nil
local subject = email:subject() or "No Subject"
local html = "<p>" .. subject .. "</p>"
4. Check special characters
-- Problem: & in HTML
local html = "<p>Tom & Jerry</p>"
-- Better: Escape &
local text = email:subject() or ""
text = text:gsub("&", "&")
local html = "<p>" .. text .. "</p>"
Plugin Actions Not Working
Symptoms
- Click on
data-actionlinks - Nothing happens
Solutions
1. Check attribute format
<!-- Correct format -->
<a href="#" data-action="mark-read-open-url" data-url="https://example.com">
Click
</a>
<!-- Wrong: Missing href -->
<a data-action="mark-read-open-url" data-url="https://example.com">
Click
</a>
<!-- Wrong: Missing data-url -->
<a href="#" data-action="mark-read-open-url">
Click
</a>
2. Check action name
Valid actions:
mark-read-open-urlopen-urlarchive-and-open
3. Check URL format
-- Must be a valid URL
data-url="https://example.com"
-- Not a relative path
data-url="/path/to/page" -- Won't work
Execution Timeout
Symptoms
- "Script execution timeout" error
- Plugin works with few emails but fails with many
Solutions
1. Optimize loops
-- Slow: String concatenation in loop
local html = ""
for _, meta in ipairs(metadata_list) do
html = html .. "<div>" .. meta:url() .. "</div>"
end
-- Fast: Use table.concat
local parts = {}
for _, meta in ipairs(metadata_list) do
parts[#parts + 1] = "<div>" .. meta:url() .. "</div>"
end
local html = table.concat(parts)
2. Avoid complex patterns
-- Slow: Complex regex-like pattern
local match = text:match("(.-)%s+(.-)%s+(.-)%s+(.-)")
-- Fast: Simple patterns
local first_word = text:match("^(%S+)")
3. Limit iterations
-- Process only first 50 URLs
local count = 0
for _, meta in ipairs(metadata_list) do
if count >= 50 then break end
-- process meta
count = count + 1
end
Memory Limit Exceeded
Symptoms
- Plugin fails with memory error
- Works for small emails, fails for large ones
Solutions
1. Don't store large strings
-- Bad: Storing entire email body
local body = email:body() -- If this existed, would be large
-- Good: Use preview (limited size)
local preview = email:preview()
2. Process in chunks
-- Instead of building huge HTML strings,
-- keep intermediate results small
Debugging Tips
1. Use print() liberally
function filter(email, user_email)
print("=== Filter called ===")
print("Email ID: " .. email:id())
print("Subject: " .. (email:subject() or "nil"))
local result = -- your filter logic
print("Result: " .. tostring(result))
return result
end
2. Check app logs
- Open Gaivota's developer tools (if available)
- Check
~/.gaivota/logs/for log files - Look for Lua-related errors
3. Test incrementally
Start simple and add complexity:
-- Step 1: Match everything
function filter(email, user_email)
return true
end
-- Step 2: Add one condition
function filter(email, user_email)
return email:from_self(user_email)
end
-- Step 3: Add more conditions
function filter(email, user_email)
return email:from_self(user_email)
and email:to_self(user_email)
end
4. Test render separately
function render(email, metadata_list)
-- Start with minimal HTML
return {
subject = "Test",
body_html = "<p>Hello World</p>"
}
-- Then add complexity gradually
end
Getting Help
If you're still stuck:
- Check the API Reference for correct method signatures
- Look at Examples for working patterns
- Review Security for what's allowed
- Open an issue on GitHub with:
- Your plugin code
- Error messages (if any)
- What you expected vs. what happened