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.