Skip to content
Concord

Troubleshooting

Concord reports problems at two moments, and each moment has its own code prefix.

The analyzer package checks your patch declarations while you build. It reports a CONCORDxxx diagnostic. The runtime checks the target method while it composes a wrapper. It throws a ConcordEmitException with a CONCxxx code.

A build-time diagnostic is the better one to get. It names the line in your source. Fix those first.

Analyzer diagnostics

Add the Concord.Analyzers package to get these. See Packages for the reference.

An error stops the build. A warning does not, but each one names a patch that behaves in a way you probably did not intend.

Code Severity What it means
CONCORD001 Error A bootstrap assembly hard-references Concord, the runtime adapter, MonoMod, or Mono.Cecil. A bootstrap runs before those assemblies load. Use reflection, or move the code into the runtime adapter.
CONCORD002 Error An [InjectField], [InjectProperty], or [InjectMethod] declaration names a member the target type does not have.
CONCORD003 Error An injected member declaration found its target, but the type, static form, return type, or signature differs.
CONCORD004 Warning The analyzer cannot resolve a string patch target, so it cannot check the declaration. Reference the target's project or assembly.
CONCORD005 Error An [Inject] names a method or constructor the target type does not have.
CONCORD006 Error The injection target name matches more than one overload. Pass parameterTypes to pick one.
CONCORD007 Error An injection parameter does not bind to a target parameter by name and type, or a ControlHandle<T> does not match the target's return type.
CONCORD008 Error A static target cannot use an instance declaration member or an injected target instance.
CONCORD009 Warning A plain field on the declaration has the same name as a target field. Plain fields become attached data. Add [InjectField] if you meant to reach the target's field.
CONCORD010 Warning Two injections declare the same target and position.
CONCORD011 Error The declaration member uses a form Concord does not support.
CONCORD012 Warning The patch target type is available at compile time. Write typeof(Target) instead of a string.
CONCORD013 Warning The target member is available at compile time. Write nameof(Member) instead of a string.
CONCORD014 Warning The declaration can inherit the target type. Derive from it and use a bare [Patch], which lets C# bind the target's members for you.
CONCORD015 Error An injection returns Control at a position other than At.Head. Only a Head injection can decide whether the original method runs.
CONCORD016 Error An around-invoke Operation parameter does not match the shape of the matched call.
CONCORD017 Error An At.Constant or At.Argument injection changes the shape of the value it receives. Take and return the matched type.
CONCORD018 Error A constant or argument injection uses the wrong [Inject] constructor for its position.
CONCORD019 Error At.Argument with arg: 0 infers the argument from the parameter type, but more than one call parameter shares that type. Pass arg:.
CONCORD020 Error The name resolves to a property that has both accessors. Write get_Name or set_Name.
CONCORD021 Error A [PatchBefore] or [PatchAfter] sits somewhere invalid, names an unknown owner, or conflicts with another rule.
CONCORD022 Error A transpiler is not static. A [Patch] declaration is abstract, so Concord can never call an instance transpiler.
CONCORD023 Error A transpiler has the wrong signature. It takes and returns IEnumerable<CodeInstruction>, with an optional second ITranspilerContext parameter.
CONCORD024 Error A transpiler reads a [Shadow] or injected member. Concord calls a transpiler instead of copying it, so those members are never rewritten for it.
CONCORD025 Error A helper, constructor, or ordinary property reads a [Shadow] or injected member. Concord rewrites those reads only inside the bodies it copies, so the helper reads the declaration's own member and gets null or a default. Pass the value in as a parameter.
CONCORD026 Error One declaration stores two different state types in one slot on one target. Every SetState and GetState<T> for a target must agree.
CONCORD027 Warning A GetState<T> has no matching SetState<T> anywhere in the declaration, so it reads back default(T).
CONCORD028 Error A [Capture] parameter sits at a position that matches no call, or at At.Around or At.Argument, which already hand you the call's arguments. Move it to the At.Head or At.Tail of an invoke or construction injection.
CONCORD029 Error [Slice] sits on a position other than an invoke or a construction. A slice bounds a call search, so nothing else can use one.
CONCORD030 Error A [Capture] ordinal falls outside the matched call's arguments. The ordinal counts from 1.
CONCORD031 Error A declaration that extends an enum also declares an [Inject] or [InjectNew] method. Move the injections to their own [Patch] class.
CONCORD032 Error An [EnumMember] field is not static, or is not typed as the extended enum. Concord assigns members to static fields of that one type.
CONCORD033 Warning An extended enum member has an initializer Concord cannot read and overwrites at apply time. Declare the member const to pin its value.
CONCORD034 Error Two extended enum members in one assembly resolve to the same id. The id is the key Concord stores the value under, so each member needs its own.
CONCORD035 Warning A static constructor reads an extended enum member. That constructor can run before Patcher.Apply assigns the field, which reads back a zero.

Concord also ships a diagnostic suppressor. It turns off CS0649, CS0169, and CS0414 on an [InjectField] declaration. Those warnings say the field is never assigned or never used, which is true of the declaration you wrote. Concord assigns and reads the real field at patch time.

Runtime error codes

When a patch cannot compose, Concord throws a ConcordEmitException with a CONCxxx code. The code identifies the condition that Concord rejected.

Code Where What it means
CONC002 Implicit field mapping A plain declaration field collides by name with a target field, but its type or static form differs. Match the target field, or rename the declaration field when it is only attached-property metadata.
CONC012 Head A non-void Head injection can cancel the target but never assigns ReturnValue. A skipped method still needs a result.
CONC013 Control or operation handle The injection stores, captures, or passes a ControlHandle or Operation family parameter instead of using its supported calls directly.
CONC014 Invoke-Around splice The call-site Invoke splice uses a computed expression instead of plain injection-parameter loads. Whole-method Around does not use this splice form; its Operation.Invoke(...) accepts computed arguments (see CONC107 for its own placement rule).
CONC015 Any position An injection method returns Control somewhere other than a Head injection. Return void at other positions. A generic ControlHandle<T> may still expose the target's return value at Return or Tail.
CONC031 Invoke or construction The call, field read, or construction you named doesn't occur in the target body. Check the declaring type and member name against the actual access. A [Slice] narrows the search, and the message then says the declared range instead of the method body.
CONC033 Invoke or construction You asked for the by-th occurrence of a call site (by counts from 1), but fewer than by matches exist. A [Slice] narrows the count to the declared range, and the message says so.
CONC034 Return A Return injection found no return in the target body to attach to.
CONC035 Return You asked for the by-th return (by counts from 1), but the method has fewer returns than that.
CONC036 Property target The property has both accessors, but nothing selected its getter or setter. Write get_Name or set_Name.
CONC037 Constant The target body contains no matching literal.
CONC038 Constant The requested 1-based literal occurrence does not exist.
CONC039 Invoke or value injection The declaration has an unsupported or mismatched call signature, operation handle, argument selector, constant type, or value method signature.
CONC051 Around More than one whole-method Around injection targets the method. Only one is allowed. Head, Return, and Tail injections can still compose alongside it; see CONC115 for what cannot.
CONC052 Patch ordering [PatchBefore] and [PatchAfter] rules form a cycle, so Concord cannot choose an order. The message lists the owner loop, such as A -> B -> A. Remove or reverse one rule. Concord leaves the installed wrapper unchanged.
CONC060 Async / iterator The target is a state machine (async/iterator) whose generated MoveNext couldn't be found.
CONC061 Generic target The target is a generic instantiation with a reference-type argument. The runtime shares one compiled body across all reference-type instantiations, so a detour would leak to every other one. Patch generic targets only at value-type instantiations.
CONC070 Injected instance The [InjectInstance] declaration is invalid. It must be a single non-static get-only property.
CONC071 Injected member declaration An [InjectField], [InjectProperty], or [InjectMethod] declaration could not find the named target member or required accessor.
CONC072 Injected member declaration An injected member has the wrong type, static form, return type, or signature. This code also covers an [InjectInstance] property that cannot receive the target type.
CONC073 Injected member declaration An injected member declaration resolves ambiguously. Rename the declaration target or use a more specific signature.
CONC074 Injected instance [InjectInstance] does not support this target, such as a static method or value-type target.
CONC106 Tail A Tail injection found no return in the target body.
CONC107 Whole-method Around The Operation handle's Invoke(...) call sits mid-expression on a target with exception handlers. Splicing the original body clears the evaluation stack on any protected-region exit, so Invoke(...) must appear only as a statement, a direct assignment, or a direct return.
CONC108 Whole-method Around The target has a ref/out/in (byref) parameter. Byref parameters are not supported by the Operation handle.
CONC109 Whole-method Around The target has a pointer, function pointer, or byref-like parameter or return type (or returns by reference). These are not supported by the Operation handle.
CONC110 Whole-method Around The target is an async method or an iterator whose body compiles to a state machine. State-machine methods are not supported by the Operation handle; patch at Head instead.
CONC111 Whole-method Around The injection method must declare exactly one Operation family parameter and no ControlHandle parameters. Whole-method Around is Operation-only.
CONC112 Whole-method Around (constructor) A constructor Around injection never calls Invoke(...). A constructor Around must invoke the original constructor exactly once.
CONC113 Whole-method Around The Operation handle's Invoke(...) call sits inside a loop. The original body can only be spliced once, so Concord rejects a loop that could re-enter the call.
CONC114 Whole-method Around The target is a static type initializer (.cctor). Type initializers have no coherent Around contract and are not supported.
CONC115 Whole-method Around A whole-method Around injection is combined with a call-site Invoke, Argument, or Constant injection on the same target. Call-site positions mutate the pre-Around spine, which does not compose with the per-copy splicing a whole-method Around performs.
CONC127 State slot One patch declaration puts two types in its state slot on one target. Every SetState and GetState<T> in the declaration must agree on one type.
CONC128 Capture A [Capture] parameter sits at a position that matches no call, or at At.Around or At.Argument, which already receive the call's arguments. Move it to At.Head or At.Tail of an invoke or construction injection.
CONC129 Capture Concord cannot tell where the captured argument finished pushing. A conditional expression in a call argument causes this. Rewrite the argument into a local before the call.
CONC130 Capture The capture ordinal runs past the last argument, the parameter type does not match the argument, or the matched site is a field read, which supplies no arguments.
CONC131 Slice The target body has no opening anchor at the requested occurrence, or the range names fromType without fromMember (or the reverse). An anchor is the pair. An anchor also has to be a call or a field read, so a construction cannot serve as one.
CONC132 Slice The target body has no closing anchor at the requested occurrence, or the range names toType without toMember (or the reverse).
CONC133 Slice The range is empty or inverted, so it closes at or before it opens. Check the two anchor occurrences against the body order.
CONC134 Slice [Slice] sits on a position that matches no call site. A range bounds a search, so it applies to invoke and construction positions only.
CONC135 Extended enum A member pins a value that is already taken on that enum.
CONC136 Extended enum The enum has no free value left in its underlying type. A [Flags] enum runs out of bits sooner, because every removed mod keeps the one it used.
CONC137 Extended enum A consumer method fits none of the four supported shapes. See Extended Enums for the table.
CONC138 Extended enum A declaration that extends an enum also carries an [Inject] or [InjectNew] method.
CONC139 Extended enum A member field is not static, or is not typed as the extended enum.
CONC140 Extended enum Two members resolve to the same id.
CONC141 Extended enum Concord could not detour one Enum method. It logs this and leaves the rest of the set installed.

Each code identifies the condition Concord rejected. Read the exception message for the target and declaration details.

Seeing the composed wrapper

Write it to your desktop

Add [PatchDebug] to a patch declaration:

[Patch]
[PatchDebug]
abstract class PricePatch : ShopItem
{
    [Inject(At.Tail, nameof(GetPrice))]
    void AfterGetPrice(ControlHandle<int> ch)
    {
        ch.ReturnValue += 5;
    }
}

When Patcher.Apply applies the declaration, Concord appends the composed wrapper IL to Concord.PatchDebug.log on the current user's desktop. Each entry names the target and includes every patch active on that target at that point. The newest entry is at the bottom of the file.

Remove [PatchDebug] when you finish troubleshooting.

Build a dump by hand

Use WrapperComposer.ComposeDump when a patch behaves in an unexpected way. It runs the same composition as the patcher. It returns the wrapper IL as text without applying it:

using System.Reflection;
using Concord.Emit;

MethodBase target = typeof(ShopItem).GetMethod(nameof(ShopItem.GetPrice))!;
MethodInfo injectionMethod = typeof(PricePatch).GetMethod(
    "AfterGetPrice",
    BindingFlags.NonPublic | BindingFlags.Instance)!;

var injections = new[]
{
    new Injection(injectionMethod, new InjectAt.Tail(), "debug", 0),
};

string il = WrapperComposer.ComposeDump(target, injections);
Console.WriteLine(il);

This is a low-level entry point. It uses Injection records and InjectAt positions instead of [Patch] and [Inject]. It is mainly useful for work on Concord itself or for hard composition bugs. The dump shows the wrapper spine, copied injection bodies, and lowered control locals. You can see where an injection landed and what the wrapper returns.

If ComposeDump throws, it reports the same CONCxxx error as a real apply. This lets you inspect the error without installing the patch.