HangfireIO/Hangfire · error · FormatException
Unable to obtain resource identifier: {ex.Message}
Error message
Unable to obtain resource identifier: {ex.Message} What it means
DisableConcurrentExecutionAttribute.GetResource (DisableConcurrentExecutionAttribute.cs:76) formats the Resource template using the job's arguments via String.Format(Resource, job.Args.ToArray()). If the Resource string contains format placeholders ({0}, {1}) that do not align with the argument count/types, or contains malformed braces, String.Format throws and the catch re-wraps it as a FormatException with the original message.
Source
Thrown at src/Hangfire.Core/DisableConcurrentExecutionAttribute.cs:76
{
throw new InvalidOperationException("Can not release a distributed lock: it was not acquired.");
}
var distributedLock = (IDisposable)value;
distributedLock.Dispose();
}
private string GetResource(Job job)
{
if (!String.IsNullOrWhiteSpace(Resource))
{
try
{
return String.Format(CultureInfo.InvariantCulture, Resource, job.Args.ToArray()).ToLowerInvariant();
}
catch (Exception ex)
{
throw new FormatException($"Unable to obtain resource identifier: {ex.Message}");
}
}
return $"{job.Type.ToGenericTypeString()}.{job.Method.Name}";
}
}
}
View on GitHub (pinned to c236dd0f93)
Solutions
- Ensure the Resource format string's placeholder indices are all within the bounds of the job method's actual parameters.
- Escape literal braces by doubling them (use '{{' and '}}') if you need literal curly braces in the resource name.
- If no interpolation is needed, omit placeholders entirely and use a plain string.
Example fix
// before: method has 1 arg but template references {1}
[DisableConcurrentExecution(Resource = "lock_{0}_{1}", timeoutSec = 30)]
public void Run(int orderId) { ... }
// after
[DisableConcurrentExecution(Resource = "lock_{0}", timeoutSec = 30)]
public void Run(int orderId) { ... } Defensive patterns
Strategy: validation
Validate before calling
// Validate the Resource template against the job method's parameter count
static void ValidateResourceTemplate(string resource, int argCount)
{
if (string.IsNullOrEmpty(resource)) return;
try { string.Format(resource, new object[argCount]); }
catch (FormatException ex)
{
throw new FormatException(
$"Resource template '{resource}' is invalid for {argCount} args: {ex.Message}");
}
}
ValidateResourceTemplate("lock_{0}_{1}", argCount: 1); // throws proactively Prevention
- Ensure Resource format placeholders ({0}, {1}) match the job method's argument count.
- Escape literal braces as '{{' and '}}' if needed in the resource name.
- If no interpolation is needed, use a plain string with no placeholders.
- Test resource templates against the actual method signature.
When it happens
Trigger: Setting [DisableConcurrentExecution(Resource = "...", ...)] with a format template whose placeholders ({0}, {1}, etc.) reference indices beyond the job method's argument count, or contain unescaped braces like '{' or '}' that are not valid format items.
Common situations: Resource template was written for a different method signature with more arguments; typo in the format string (e.g. 'lock_{0}_{5}' when there are only 2 args); a literal brace in the resource name that String.Format interprets as a placeholder.
Related errors
- Timeout argument value should be greater than zero.
- Attempts value must be equal or greater than zero.
- DelaysInSeconds value must be an array of non-negative numbe
- Can not release a distributed lock: it was not acquired.
- Display name is empty
AI-assisted analysis of HangfireIO/Hangfire@c236dd0f93 (2026-08-13).
Data as JSON: /api/errors/2dfdd81f3e4441aa.
Report an issue: GitHub.