Skip to main content

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("&", "&amp;")
local html = "<p>" .. text .. "</p>"

Plugin Actions Not Working

Symptoms

  • Click on data-action links
  • 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-url
  • open-url
  • archive-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:

  1. Check the API Reference for correct method signatures
  2. Look at Examples for working patterns
  3. Review Security for what's allowed
  4. Open an issue on GitHub with:
    • Your plugin code
    • Error messages (if any)
    • What you expected vs. what happened