Error when loading SNMP check plugin: value of 'detect' keywords third element must be a boolean: None

CMK version: 2.4.0p20

Error message: value of ‘detect’ keywords third element must be a boolean: None

Output of “cmk --debug -vvn hostname”: (If it is a problem with checks or plugins)

Traceback (most recent call last):
File “/omd/sites/Demo/bin/cmk”, line 135, in
errors = config.load_all_plugins(
^^^^^^^^^^^^^^^^^^^^^^^^
File “/omd/sites/Demo/lib/python3/cmk/base/config.py”, line 1425, in load_all_plugins
errors = agent_based_register.load_all_plugins(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/omd/sites/Demo/lib/python3.12/contextlib.py”, line 81, in inner
return func(*args, **kwds)
^^^^^^^^^^^^^^^^^^^
File “/omd/sites/Demo/lib/python3/cmk/base/api/agent_based/register/_discover.py”, line 76, in load_all_plugins
_register_plugin_by_type(location, plugin, validate=raise_errors)
File “/omd/sites/Demo/lib/python3/cmk/base/api/agent_based/register/_discover.py”, line 112, in _register_plugin_by_type
register_snmp_section(plugin, location, validate=validate)
File “/omd/sites/Demo/lib/python3/cmk/base/api/agent_based/register/_discover.py”, line 146, in register_snmp_section
section_plugin = create_snmp_section_plugin(section, location, validate=validate)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File “/omd/sites/Demo/lib/python3/cmk/base/api/agent_based/register/section_plugins.py”, line 289, in create_snmp_section_plugin
_validate_detect_spec(snmp_section_spec.detect)
File “/omd/sites/Demo/lib/python3/cmk/base/api/agent_based/register/section_plugins.py”, line 177, in _validate_detect_spec
raise TypeError(
TypeError: value of ‘detect’ keywords third element must be a boolean: None

Hello everyone,

I am a beginner with Checkmk and I am currently trying to develop a custom SNMP check plugin using the cmk.agent_based.v2 API. When I run plugin detection, Checkmk fails to load the plugin and returns the following error.

My plugin code

#!/usr/bin/env python3

from cmk.agent_based.v2 import (
    CheckPlugin,
    startswith,
    Service,
    Result,
    State,
    SNMPSection,
    SNMPTree,
    all_of,
    contains,
)

def parse_power(string_table):
    data = {}
    for idx, row in enumerate(string_table, start=1):
       if len(row) < 3:
            continue
            data[str(idx)] = {
            "status": row[0],
            "ps_serial": row[1],
            "switch_serial": row[2],
        }
    return data

def discover_power(section):
    for item in section:
        yield Service(item=item)

def check_power(item, section):
    data = section[item]
    status = data["status"]
    ps_sn = data["ps_serial"]
    sw_sn = data["switch_serial"]

    if status == "Good":
        yield Result(
            state=State.OK,
            summary=f"Power {item} OK | PSU SN: {ps_sn} | Switch SN: {sw_sn}",
        )
    else:
        yield Result(
            state=State.CRIT,
            summary=f"Power {item} FAILED ({status}) | PSU SN: {ps_sn}",
        )

snmp_section_power = SNMPSection(
    name = "power_section",
    parse_function = parse_power,
    
        detect=all_of(
        startswith(".1.3.6.1.2.1.1.1.0", "LANCOM"),
        contains(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.2356"),
    ),
    
    fetch = [
        SNMPTree(
                base = '.1.3.6.1.4.1.2356.16.1.1.1.1',
                oids = ['27.0','33.0','3.0'],
                ),
                ]
)

check_plugin_power = CheckPlugin(
    name = "Power_Check",
    sections = ["power_section"],
    service_name = "Power Supply %s",
    discovery_function = discover_power,
    check_function = check_power,
)

SNMP information from the Switch

.1.3.6.1.2.1.1.1.0 LANCOM XS-5116QF --> SNMPv2-MIB::sysDescr.0
.1.3.6.1.2.1.1.2.0 .1.3.6.1.4.1.2356.16.8.5116 --> SNMPv2-MIB::sysObjectID.0
.
.
.
.1.3.6.1.2.1.16.1.1.1.1.26 26 --> RMON-MIB::etherStatsIndex.26
.1.3.6.1.2.1.16.1.1.1.1.27 27 --> RMON-MIB::etherStatsIndex.27
.1.3.6.1.2.1.16.1.1.1.1.28 28 --> RMON-MIB::etherStatsIndex.28
.1.3.6.1.2.1.16.1.1.1.1.29 29 --> RMON-MIB::etherStatsIndex.29
.1.3.6.1.2.1.16.1.1.1.1.30 30 --> RMON-MIB::etherStatsIndex.30
.1.3.6.1.2.1.16.1.1.1.1.31 31 --> RMON-MIB::etherStatsIndex.31
.1.3.6.1.2.1.16.1.1.1.1.32 32 --> RMON-MIB::etherStatsIndex.32
.1.3.6.1.2.1.16.1.1.1.1.33 33 --> RMON-MIB::etherStatsIndex.33


Any guidance would be greatly appreciated.

Hi and wellcome to CMK,

there are three issues in your plugin code:

1. The crash: startswith needs an explicit third argument

Internally, Checkmk’s detect primitives store a 3-tuple of (oid, pattern, re_escape).
The re_escape parameter must be a boolean — if it ends up as None, you get exactly
the error you see. Pass it explicitly:

detect=all_of(
    startswith(".1.3.6.1.2.1.1.1.0", "LANCOM", True),
    contains(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.2356", False),
),

Use True for startswith (plain string match, no regex needed) and False for
contains since the OID string contains dots which are regex special characters.

API reference for startswith() and contains():

2. The parse function is broken (indentation bug)

for idx, row in enumerate(string_table, start=1):
   if len(row) < 3:
        continue
        data[str(idx)] = { ...  # ← unreachable: inside the if-block after continue

The data[str(idx)] assignment sits inside the if-block after continue, so it is
never executed and the parser always returns an empty dict. Correct indentation:

for idx, row in enumerate(string_table, start=1):
    if len(row) < 3:
        continue
    data[str(idx)] = {
        "status": row[0],
        "ps_serial": row[1],
        "switch_serial": row[2],
    }

3. SNMPTree OIDs should not have a .0 suffix

In SNMPTree, oids contains sub-OIDs relative to base. The .0 suffix is for
scalar OIDs outside of tables — for a table walk use plain column indices:

oids = ['27', '33', '3']

API reference for SNMPTree:

After fixing all three, test locally with:

cmk --detect-plugins=power_section -vvn <hostname>

Good luck!

That is completely wrong.

The Docu link shows how it should look like

DETECT = startswith("1.2.3", "Sol")
1 Like

Hi,

sorry for the incorrect advice in my previous post — Andreas is right, startswith and contains in the v2 API take only two arguments (OID and pattern). My suggestion to add a third re_escape argument was wrong.

copy-paste issue :ogre:

Looking at your code again, the detect block is actually correct as written:

detect=all_of(
    startswith(".1.3.6.1.2.1.1.1.0", "LANCOM"),
    contains(".1.3.6.1.2.1.1.2.0", ".1.3.6.1.4.1.2356"),
),

The error third element must be a boolean: None most likely comes from a different plugin file in the same folder that has a malformed detect spec. To find the actual culprit, run:

cmk -L 2>&1 | grep -i error

This lists all plugin load errors with the exact filename. Once you know which file is causing the crash, you can fix or remove it.

The two real bugs in your code are still valid though:

Indentation bug in parse_powerdata[str(idx)] is unreachable because it sits inside the if-block after continue. The parser always returns an empty dict:

for idx, row in enumerate(string_table, start=1):
    if len(row) < 3:
        continue
    data[str(idx)] = {          # ← must be at this indentation level
        "status": row[0],
        "ps_serial": row[1],
        "switch_serial": row[2],
    }

SNMPTree OIDs should not have a .0 suffix — for table columns use plain integers:

oids = ['27', '33', '3']

Reference: Developing SNMP-based check plug-ins

Greetz

1 Like

Thank you very much for your help and guidance. :blush:

With your suggestions, my plugin code now loads and runs correctly. However, I still need to work more on the parsing logic to get the expected monitoring results.

Below is the output when I run the debug command:

value store: loading from disk
Checkmk version 2.4.0p20

  • FETCHING DATA
    Source: SourceInfo(hostname=‘test-Host’, ipaddress=‘172.1.1.', ident=‘snmp’, fetcher_type=<FetcherType.SNMP: 7>, source_type= <SourceType.HOST: 1>)
    [cpu_tracking] Start [77baeb2b8d10]
    Read from cache: SNMPFileCache(path_template=/omd/sites/Demo/tmp/check_mk/data_source_cache/snmp/{mode}/test-Host, max_age=MaxA ge(checking=0, discovery=90.0, inventory=90.0), simulation=False, use_only_cache=False, file_cache_mode=6)
    Not using cache (Mode Mode.FORCE_SECTIONS)
    power_section: Fetching data (SNMP walk cache cleared)
    Running 'snmpbulkwalk -Cr10 -v2c -c SNMPDemo -m “” -M “” -Cc -OQ -OU -On -Ot 172.1.1.
    .1.3.6.1.4.1.2356.16.1.1.1.1.27.0’
    Running ‘snmpbulkwalk -Cr10 -v2c -c SNMPDemo -m “” -M “” -Cc -OQ -OU -On -Ot 172.1.1.* .1.3.6.1.4.1.2356.16.1.1.1.1.33.0’
    Running ‘snmpbulkwalk -Cr10 -v2c -c SNMPDemo -m “” -M “” -Cc -OQ -OU -On -Ot 172.1.1.* .1.3.6.1.4.1.2356.16.1.1.1.1.3.0’
    Not using cache (Mode Mode.FORCE_SECTIONS)
    [cpu_tracking] Stop [77baeb2b8d10 - Snapshot(process=posix.times_result(user=0.0, system=0.0, children_user=0.01, children_system=0.01, elap sed=0.4100000001490116))]
    Source: SourceInfo(hostname=‘test-Host’, ipaddress=‘172.1.1.', ident=‘piggyback’, fetcher_type=<FetcherType.PIGGYBACK: 4>, so urce_type=<SourceType.HOST: 1>)
    [cpu_tracking] Start [77baeb2a2f30]
    Read from cache: NoCache(path_template=/dev/null, max_age=MaxAge(checking=0.0, discovery=0.0, inventory=0.0), simulation=False, use_only_cac he=False, file_cache_mode=1)
    0 piggyback files for ‘test-Host’.
    0 piggyback files for '172.1.1.
    ’.
    Get piggybacked data
    [cpu_tracking] Stop [77baeb2a2f30 - Snapshot(process=posix.times_result(user=0.0, system=0.0, children_user=0.0, children_system=0.0, elapse d=0.009999999776482582))]
    [cpu_tracking] Start [77baefd7b6e0]
  • PARSE FETCHER RESULTS
    HostKey(hostname=‘test-Host’, source_type=<SourceType.HOST: 1>) → Add sections: [‘power_section’]
    HostKey(hostname=‘test-Host’, source_type=<SourceType.HOST: 1>) → Add sections:
    Received no piggyback data
    0 piggyback files for ‘test-Host’.
    [cpu_tracking] Stop [77baefd7b6e0 - Snapshot(process=posix.times_result(user=0.010000000000000009, system=0.0, children_user=0.0, children_s ystem=0.0, elapsed=0.0))]
    [snmp] Success, [piggyback] Success (but no data found for this host), execution time 0.4 sec | execution_time=0.420 user_time=0.010 system_ time=0.000 children_user_time=0.010 children_system_time=0.010 cmk_time_snmp=0.390 cmk_time_agent=0.010

So the SNMP section is detected and data is fetched successfully.

However, in the Checkmk GUI I now receive the following error when opening the service discovery preview:

Error:

Error running automation call service-discovery-preview (exit code 2):
'list' object has no attribute 'base'

Because of this error I cannot run service discovery for any host.

Does this error indicate a problem with how the SNMPTree is defined in my plugin, or could it be related to how the parsed data is returned from the parse function?

Any advice on how to debug or fix this would be greatly appreciated.

Hi,

good progress — the section is now loading and fetching data correctly!

The new error 'list' object has no attribute 'base' comes from two remaining issues
in your code:

1. Wrong string_table structure in parse_function

Since you use SNMPSection (not SimpleSNMPSection) with fetch = [SNMPTree(...)],
Checkmk passes a list of tables to your parse function — one per SNMPTree in the
fetch list. So string_table is actually a Sequence[StringTable], not a plain
StringTable. You need to access the first table explicitly:

def parse_power(string_table):
    data = {}
    for idx, row in enumerate(string_table[0], start=1):  # ← string_table[0]
        if len(row) < 3:
            continue
        data[str(idx)] = {
            "status": row[0],
            "ps_serial": row[1],
            "switch_serial": row[2],
        }
    return data

If you only have one SNMPTree, you can also switch to SimpleSNMPSection which
passes a plain StringTable directly — no [0] needed:

2. Indentation bug still present

As mentioned before, data[str(idx)] must be outside the if-block:

for idx, row in enumerate(string_table[0], start=1):
    if len(row) < 3:
        continue
    data[str(idx)] = {       # ← at this indentation level, not inside the if
        "status": row[0],
        ...
    }

Without this fix the parser always returns {}, which causes the service discovery
to fail.

Reference:

Greetz

1 Like