These are the failures we keep seeing when an AI is asked for a WoW addon and nobody tells it the client is Forever. Each one has the line it will emit, and the line that survives.

This breakdown is part of the WoW Forever AI Addon Guide. To understand why these failures occur under the hood, read WoW Forever Addon Patterns and consult the Forever API Cheatsheet. You can also prevent them upfront using our Ready-to-Use AI Prompts.

Twelve traps

01. Shipping Interface 120001 or 11503

Models copy Midnight guides or Classic templates. Forever beta's interface is 16001. The wrong number flags the addon out of date or hides it from the client you are testing.

It writes:

## Interface: 120001

You ship:

## Interface: 16001

02. Branching on WOWPROJECTID

Build 1.60.1.69913 reports 1, the same value libraries use for retail. A Classic-only branch never runs. A retail-only branch may run features Forever cannot support.

It writes:

if WOW_PROJECT_ID ~= WOW_PROJECT_MAINLINE then
    -- hoped this meant Classic. Forever never arrives.
end

You ship:

local iface = select(4, GetBuildInfo())
-- 16001 was Forever beta. Retest at launch.
if iface == 16001 then
    ns:UseForeverPath()
end

03. Adding one to UnitHealth

The one-liner from the beta: type(UnitHealth('player')) prints number, and h + 1 fails. Third-party raid frames that compute a percent throw "attempt to perform arithmetic on a secret number value".

It writes:

local h = UnitHealth("player")
local pct = h / UnitHealthMax("player")

You ship:

local bar = ns.healthBar
bar:SetMinMaxValues(0, UnitHealthMax("player"))
bar:SetValue(UnitHealth("player"))

04. Printing a name that might be secret

Unit and creature names can be secret strings. Concat or print throws and can blank the frame. Degrade gracefully on purpose.

It writes:

print("Hello " .. UnitName("player"))

You ship:

local name = UnitName("player")
if type(issecretvalue) == "function" and issecretvalue(name) then
    name = "adventurer"
end
print("Hello " .. name)

05. GetSpellInfo and GetItemInfo

Both were absent on build 69913, along with GetSpellBookItemName and the old talent globals. The C_ replacements return one table.

It writes:

local name, _, icon = GetSpellInfo(8690)

You ship:

local info = C_Spell.GetSpellInfo(8690)
if not info then
    C_Spell.RequestLoadSpellData(8690)
    return
end
local name, icon = info.name, info.iconID

06. A damage meter, politely disguised

There is no combat log for addons. Sums of damage are both blocked and beside the point: Forever ships a built-in meter and a cooldown manager.

It writes:

frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

You ship:

-- Presentation only. Let the built-in meter count.
frame:RegisterUnitEvent("UNIT_HEALTH", "player")

07. Click-cast via a secure snippet

Snippet compilation failed on the observed beta. Even if that is fixed by launch, writing secure attributes onto Blizzard unit frames is a known protected action. pcall does not clear it.

It writes:

SecureHandlerWrapScript(button, "OnClick", button, [[
    self:SetAttribute("type", "spell")
]])

You ship:

-- Own button, plain script, no secure attribute
-- on a Blizzard unit frame.
button:SetScript("OnClick", function()
    -- open your panel, do not cast for the player
end)

08. SavedVariables at file scope

They are nil while the file loads. MyAddonDB = MyAddonDB or {} at the top runs before the real table exists and can clobber it. On top of that, the Forever beta has failed to restore SavedVariables that were written correctly.

It writes:

MyAddonDB = MyAddonDB or { enabled = true }

You ship:

function events:ADDON_LOADED(loaded)
    if loaded ~= addonName then return end
    MyAddonDB = MyAddonDB or {}
    if MyAddonDB.enabled == nil then
        MyAddonDB.enabled = true
    end
    ns.db = MyAddonDB
end

See the full SavedVariables lifecycle in our SavedVariables template.

09. Cancelling C_Timer.After

After returns nothing. The timer object comes from NewTimer or NewTicker.

It writes:

local t = C_Timer.After(5, func)
t:Cancel()

You ship:

local t = C_Timer.NewTimer(5, func)
t:Cancel()

10. Protected calls hidden in pcall

ChatFrame_OpenChat, C_SuperTrack.SetSuperTrackedUserWaypoint, and secure attributes on Blizzard frames can fail without a Lua error. The only symptom is an interface-action counter that /reload does not reset.

It writes:

pcall(C_SuperTrack.SetSuperTrackedUserWaypoint, 1, x, y)

You ship:

C_Map.SetUserWaypoint(uiMapID, position)

11. Faction tests that lie

On this client, UnitIsEnemy and UnitCanAttack mean "attackable right now". An unflagged enemy, a sanctuary, or another shard answers false. They can also be secret booleans.

It writes:

if UnitIsEnemy("player", unit) then

You ship:

local them = UnitFactionGroup(unit)
local mine = UnitFactionGroup("player")
if them and mine and them ~= mine then

12. Slash commands that never register

The global must be SLASH_NAME1 and the table key must be the same NAME in uppercase. Locals and mismatched case are invisible.

It writes:

local SLASH_fb1 = "/fb"
SlashCmdList.fb = function() end

You ship:

SLASH_FOREVERBEACON1 = "/fb"
SlashCmdList["FOREVERBEACON"] = function(msg)
end

For complete, copy-pasteable implementations of safe bars, combat queues, and addon compartments, visit Production Code Templates.