CUR prefix and appear in the Error List / build output like any other C# diagnostic.
Handlers & general
CUR001 — Unsupported handler signature (Warning)
CUR001 — Unsupported handler signature (Warning)
[XxxHandler] attribute returns something other than Task<Result>, ValueTask<Result>, Task, or ValueTask.Fix: Change the return type to one of the four supported types. Task/ValueTask (non-generic) stop routing after the handler completes; Task<Result>/ValueTask<Result> let you control routing with Result.StopRouting / Result.ContinueRouting.Commands
CUR002 — Command filter on non-message payload (Warning)
CUR002 — Command filter on non-message payload (Warning)
[Command] is applied to a handler whose update payload is not Message (e.g. [CallbackQueryHandler, Command(...)]).Fix: Remove [Command] from non-message handlers. Commands only make sense for Message payloads. The [Command] attribute is ignored and the handler runs without a command guard.CUR003 — Arg, Rest or Parse on non-command handler (Warning)
CUR003 — Arg, Rest or Parse on non-command handler (Warning)
[Command] attribute (or has [Command] without Aliases) is annotated with [Arg], [Rest], or [Parse]. The parameter is treated as a normal DI service.Fix: Add [Command(Aliases = [...])] to the handler, or remove the [Arg]/[Rest]/[Parse] annotation and inject the value from DI instead.CUR004 — Non-constant command prefix (Warning)
CUR004 — Non-constant command prefix (Warning)
Prefix named argument on [Command] is not a constant char literal. The generator falls back to '/'.Fix: Use a constant character literal: Prefix = '!'.CUR005 — Incompatible argument parameter type (Warning)
CUR005 — Incompatible argument parameter type (Warning)
[Arg] has a type that is not T, Nullable<T>, or object. The parameter is treated as a DI dependency.Fix: Change the parameter type to the argument type, T?, or object. Primitive types (int, long, Guid, enums, and anything with an accessible TryParse) are supported.CUR006 — Multiple Rest parameters (Warning)
CUR006 — Multiple Rest parameters (Warning)
[Rest] parameter. Extra [Rest] parameters are treated as DI dependencies, which will fail at runtime because string is not registered.Fix: Keep only one [Rest] parameter per handler.CUR007 — Rest parameter is not a string (Warning)
CUR007 — Rest parameter is not a string (Warning)
[Rest] is applied to a parameter whose type is not string. The parameter is treated as a DI dependency.Fix: Change the parameter type to string.CUR008 — Parse after Rest (Warning)
CUR008 — Parse after Rest (Warning)
[Parse] parameter is declared after a [Rest] parameter. Because [Rest] consumes all remaining text, nothing is left for [Parse]. The parameter is treated as a DI dependency.Fix: Reorder parameters so all [Parse] parameters appear before [Rest].CUR009 — Parse parameter is not a string (Warning)
CUR009 — Parse parameter is not a string (Warning)
[Parse] is applied to a parameter whose type is not string. The parameter is treated as a DI dependency.Fix: Change the parameter type to string.CUR020 — Command alias is not a valid BotFather command (Error)
CUR020 — Command alias is not a valid BotFather command (Error)
[Command(Aliases = [...])] does not match ^[a-z0-9_]{1,32}$ — BotFather’s required format for command names.Fix: Use only lowercase ASCII letters, digits, and underscores; keep the name between 1 and 32 characters.CUR021 — Command has no BotFather description (Warning)
CUR021 — Command has no BotFather description (Warning)
[Command] with Aliases has no Description set and IsHidden is false. The command cannot be synchronized with BotFather.Fix: Add Description = "Short description" to [Command], or set IsHidden = true if you intentionally do not want the command in the menu.CUR022 — Command description exceeds 256 characters (Error)
CUR022 — Command description exceeds 256 characters (Error)
[Command].Description is longer than 256 characters. BotFather rejects descriptions that exceed this limit.Fix: Shorten the description to at most 256 characters.CUR023 — Duplicate command in the same scope and language (Warning)
CUR023 — Duplicate command in the same scope and language (Warning)
CommandScope and LanguageCode combination. Only the first is sent to BotFather.Fix: Deduplicate the alias across the affected handlers, or assign different Scope/LanguageCode values to each so they target different BotFather menus.CUR052 — Command alias starts with the prefix (Warning)
CUR052 — Command alias starts with the prefix (Warning)
[Command(Aliases = [...])] starts with the same character as the command Prefix (e.g. Aliases = ["/start"] with the default / prefix). The matcher looks for the prefix followed by the alias, so //start can never match — the command is dead.Fix: Drop the leading prefix from the alias: Aliases = ["start"]. This check does not apply to whitespace prefixes (BotFather-style mention commands).State
CUR010 — State type argument must be an enum (Error)
CUR010 — State type argument must be an enum (Error)
TEnum of [State<TEnum>] or [NoState<TEnum>] is not an enum type. The attribute is ignored.Fix: Use an enum type as the type argument: [State<RegistrationStep>(RegistrationStep.AwaitingAge)].CUR011 — Mutually exclusive state conditions (Warning)
CUR011 — Mutually exclusive state conditions (Warning)
[State<TEnum>(value)] and [NoState<TEnum>] for the same (TEnum, StateKey) pair. The conditions are AND-ed, so the handler never matches.Fix: Remove one of the conflicting attributes.Filters
CUR012 — Generic filter class (Warning)
CUR012 — Generic filter class (Warning)
IUpdateFilter has one or more generic type parameters. The generator cannot emit a wrapper attribute for a generic class; the filter is skipped.Fix: Make the filter class non-generic. If you need parameterized behavior, use constructor injection instead.Awaits
CUR013 — Update awaiter without an await site (Warning)
CUR013 — Update awaiter without an await site (Warning)
IUpdateAwaiter parameter but the generator found no WaitFor*Async(...) call on it in the method body. If the handler were to call WaitFor*Async at runtime without the corresponding branch in the router, the polling loop would deadlock.Fix: Call WaitFor*Async directly on the IUpdateAwaiter parameter (not through a wrapper method — the generator does not follow indirection by design). If you do not intend to suspend the handler, remove the IUpdateAwaiter parameter.CUR014 — TextMatches on a non-Message await target (Warning)
CUR014 — TextMatches on a non-Message await target (Warning)
.TextMatches(...) is chained on a WaitFor*Async call whose target DTO is not a message-shaped payload (Message). The text check can never match; no implicit await branch is generated.Fix: Remove .TextMatches(...) from the chain, or change the awaited type to a message-shaped DTO.CUR015 — Unknown filter in await chain (Warning)
CUR015 — Unknown filter in await chain (Warning)
.With*() call in an await chain references a filter name that could not be resolved to a known IUpdateFilter class. The filter check is not emitted into the await branch.Fix: Ensure the filter class exists in the current compilation (or a referenced library), is concrete (non-abstract), non-generic, and implements IUpdateFilter. Check for typos in the With* extension name.CUR016 — Unsupported Where predicate (Warning)
CUR016 — Unsupported Where predicate (Warning)
.Where(...) predicate in an await chain does not meet the generator’s static-lambda constraints. The predicate must be static, non-async, expression-bodied, and reference only the parameter and literals. The predicate is not emitted into the await branch.Fix: Add the static modifier, remove any async modifier, convert to an expression body, and ensure the body references only the lambda parameter (no statics, no captured variables).Patterns (callback-data routing)
CUR024 — Callback pattern can never fit callback_data (Error)
CUR024 — Callback pattern can never fit callback_data (Error)
[Pattern] template is already longer than Telegram’s 64-byte callback_data limit. No update can ever match the template.Fix: Shorten the literal segments of the template so the skeleton fits within 64 bytes.CUR025 — Callback pattern placeholders can never fit callback_data (Error)
CUR025 — Callback pattern placeholders can never fit callback_data (Error)
CUR026 — Callback pattern placeholder has no [Arg] parameter (Error)
CUR026 — Callback pattern placeholder has no [Arg] parameter (Error)
{name} placeholder in a [Pattern] template has no matching [Arg]-annotated parameter on the handler.Fix: Add [Arg] T name to the handler parameter list, or remove the {name} placeholder from the template.CUR027 — [Arg] parameter is not captured by the callback pattern (Error)
CUR027 — [Arg] parameter is not captured by the callback pattern (Error)
[Arg]-annotated parameter but the [Pattern] template contains no matching {name} placeholder.Fix: Add a {paramName} or {paramName:constraint} placeholder to the template, or remove the [Arg] annotation and let the parameter be resolved from DI.CUR028 — [Arg] type has no accessible TryParse (Error)
CUR028 — [Arg] type has no accessible TryParse (Error)
[Arg] parameter used in a [Pattern] has no accessible static TryParse(string, out T) or TryParse(ReadOnlySpan<char>, out T) method. The parameter binds to default(T) in the generated code.Fix: Use a type that has an accessible TryParse, or add one to a custom type. BCL numerics, bool, Guid, DateTime, and all enums are automatically supported.CUR029 — [Pattern] requires a CallbackQuery handler (Error)
CUR029 — [Pattern] requires a CallbackQuery handler (Error)
[Pattern] is applied to a handler that is not a [CallbackQueryHandler]. The pattern is ignored.Fix: Remove [Pattern] from the handler, or change the handler attribute to [CallbackQueryHandler].CUR030 — Callback pattern constraint does not match parameter type (Error)
CUR030 — Callback pattern constraint does not match parameter type (Error)
{id:int}) does not match the C# type of the corresponding [Arg] parameter (e.g. the parameter is string), or the constraint name is unknown.Fix: Align the constraint with the parameter type, or remove the constraint to accept any string segment.CUR031 — Malformed callback pattern (Error)
CUR031 — Malformed callback pattern (Error)
[Pattern] template string is structurally invalid — e.g. unbalanced braces, empty segments, a wildcard {*name} that is not the last segment, or an empty placeholder name.Fix: Correct the template syntax. Examples of valid forms: "item:{id}", "cart:{id:int}:checkout", "logs:{*path}".Exception handling
CUR032 — Exception handler parameter cannot be resolved in the error path (Error)
CUR032 — Exception handler parameter cannot be resolved in the error path (Error)
[ExceptionHandler] method is update-bound (e.g. typed as Message, CallbackQuery) or uses [Key] without a constant key. There is no current update in the error path, so these parameters cannot be resolved.Fix: Remove update-bound parameters from the exception handler. Allowed parameter types are Exception, HandleErrorSource, ITelegramBotClient, CancellationToken, and DI services (optionally keyed with a constant [Key]).CUR033 — Only one [ExceptionHandler] is allowed (Error)
CUR033 — Only one [ExceptionHandler] is allowed (Error)
[ExceptionHandler]. Only the first declaration is used.Fix: Remove [ExceptionHandler] from all but one method.CUR034 — Exception handler must return Task or ValueTask (Error)
CUR034 — Exception handler must return Task or ValueTask (Error)
[ExceptionHandler] method does not return Task or ValueTask. The attribute is ignored.Fix: Change the return type to Task or ValueTask.Keyboards
CUR017 — Unknown keyboard placeholder (Warning)
CUR017 — Unknown keyboard placeholder (Warning)
{parameter} placeholder in [CallbackButton].CallbackData does not match any parameter of the declaring partial method. The generated implementation will not compile until the mismatch is resolved.Fix: Either add a parameter with the matching name to the method, or remove or rename the placeholder.CUR018 — Generated keyboard must return InlineKeyboardMarkup (Error)
CUR018 — Generated keyboard must return InlineKeyboardMarkup (Error)
[CallbackButton] returns a type other than InlineKeyboardMarkup. The generator only implements InlineKeyboardMarkup returns.Fix: Change the return type to Telegram.Bot.Types.ReplyMarkups.InlineKeyboardMarkup.CUR019 — Generated keyboard member must be partial (Warning)
CUR019 — Generated keyboard member must be partial (Warning)
[CallbackButton] is not marked partial (or its containing type is not partial). The generator cannot provide the implementation.Fix: Add partial to the method (or property in C# 13) and to every enclosing type.CUR050 — callback_data literal is invalid (Error)
CUR050 — callback_data literal is invalid (Error)
callback_data string passed to InlineKeyboardButton.WithCallbackData(...), InlineKeyboardBuilder.CallbackButton(...), or an InlineKeyboardButton object initializer is empty or longer than 64 bytes. The Bot API requires 1–64 bytes, so the button fails at runtime.Fix: Keep callback data between 1 and 64 UTF-8 bytes. Note that the limit is in bytes, not characters — non-ASCII data fills the budget faster.CUR055 — callback_data built by string interpolation (Info)
CUR055 — callback_data built by string interpolation (Info)
callback_data argument (same call sites as CUR050) is an interpolated string ($"item:{id}"). Interpolated data bypasses compile-time length checks, is parsed by hand at the receiving handler, and drifts easily.Fix: Consider a generated callback keyboard with [CallbackButton] placeholders, or [Pattern] with CallbackPatternMatcher for typed, length-checked parsing:Guest messages
CUR046 — GuestMessage handler posts to a chat (Warning)
CUR046 — GuestMessage handler posts to a chat (Warning)
[GuestMessageHandler] calls a chat-bound send method (SendMessage, SendPhoto, SendVideo, SendAudio, SendDocument, SendSticker, SendDice, SendPoll, SendLocation, SendVenue, SendContact, SendPaidMedia, and the rest of the Send* family). Guest messages are not addressed to a bot chat — replies must go through AnswerGuestQuery with the message’s GuestQueryId. A regular send targets the wrong chat or fails silently.Fix: Answer with AnswerGuestQuery instead:CUR047 — GuestMessage handler can answer the guest query more than once (Warning)
CUR047 — GuestMessage handler can answer the guest query more than once (Warning)
AnswerGuestQuery can run twice for one update — sequential calls, two calls in one loop body, or a call before and another inside a loop. A guest query can be answered only once; every call after the first fails.Not reported: two calls on mutually exclusive if/else branches (only one runs per update), or a single call inside a loop whose iterations may await new guest messages. Local functions fall back to straight-line counting.Fix: Keep a single AnswerGuestQuery call per update, or move the decision into one answer call.Callback queries
CUR048 — CallbackQuery handler can answer the callback query more than once (Warning)
CUR048 — CallbackQuery handler can answer the callback query more than once (Warning)
AnswerCallbackQuery can run twice for one update — sequential calls, two calls in one loop body, or a call before and another inside a loop. A callback query can be answered only once; every call after the first fails.Not reported: two calls on mutually exclusive if/else branches (only one runs per update). Local functions fall back to straight-line counting; lambda bodies are analyzed via their own control-flow graph.Fix: Keep a single AnswerCallbackQuery call per update, or move the decision into one answer call.CUR056 — CallbackQuery handler never answers the query (Info)
CUR056 — CallbackQuery handler never answers the query (Info)
[CallbackQueryHandler] has no reachable AnswerCallbackQuery call at all. The button on the client’s side keeps showing a loading indicator until the Bot API times it out.Fix: If the user should get feedback (button un-press, toast notification), call AnswerCallbackQuery — even with no text:Inline queries
CUR049 — InlineQuery handler can answer the inline query more than once (Warning)
CUR049 — InlineQuery handler can answer the inline query more than once (Warning)
AnswerInlineQuery can run twice for one update. An inline query can be answered only once; every call after the first fails.Not reported: two calls on mutually exclusive if/else branches (only one runs per update). Local functions fall back to straight-line counting; lambda bodies are analyzed via their own control-flow graph.Fix: Keep a single AnswerInlineQuery call per update.CUR051 — AnswerInlineQuery result list exceeds 50 items (Error)
CUR051 — AnswerInlineQuery result list exceeds 50 items (Error)
AnswerInlineQuery call passes more than 50 inline results inline — as an array, collection expression, or a local variable initialized with one. The Bot API rejects more than 50 results, so the call fails at runtime.Fix: Cap the result list at 50 items. Collections built with spread elements ([..a, ..b]) or other non-literal expressions are not counted.Async hygiene
CUR053 — Handler blocks on asynchronous work (Info)
CUR053 — Handler blocks on asynchronous work (Info)
.Result, calls .Wait(), or .GetAwaiter().GetResult() on a task-like value. Blocking synchronously stalls the sequential update loop — one slow handler delays every pending update.Fix: Make the handler async and await the operation instead.CUR054 — Handler creates its own TelegramBotClient (Info)
CUR054 — Handler creates its own TelegramBotClient (Info)
new TelegramBotClient(...). A per-call client bypasses the shared, configured HttpClient (proxy, retry, logging, rate-limit handlers) registered by AddTelegramBot, and breaks PolyTests test doubles.Fix: Inject the shared client through the handler parameter list:Rate limiting
CUR035 — Invalid [Throttled] configuration (Warning)
CUR035 — Invalid [Throttled] configuration (Warning)
[Throttled] attribute has an invalid or non-constant value for Limit, PeriodMilliseconds, Scope, or Action. The affected value is clamped to its default (Limit=1, PeriodMilliseconds=1000, Scope=User, Action=Ignore).Fix: Use positive integer literals for Limit and PeriodMilliseconds, and enum literals for Scope and Action.Library projects
CUR036 — Handlers are not routed in library projects (Warning)
CUR036 — Handlers are not routed in library projects (Warning)
[XxxHandler]) is declared in a project with OutputType=Library. Library projects do not get a generated router, so the handler never runs.Fix: Move the handler to an executable project (OutputType=Exe or WinExe). Libraries may only contribute filters, keyboards, and With* extensions. See Library vs Executable for details.