Recovering ISM policy detachment after template updates

After an index template is edited — a mapping tweak, a shard-count change, a settings cleanup — the very next rollover can quietly produce an unmanaged index: it comes up with no OpenSearch Index State Management (ISM) policy attached, never transitions, and grows without bound while every older index in the series keeps tiering normally. The usual cause is that the template edit dropped the plugins.index_state_management.policy_id setting (or the policy’s ism_template no longer matches the pattern), so ISM has nothing to auto-attach at index creation. This procedure detects the unmanaged indices, re-attaches the policy with _plugins/_ism/add, and repairs the template so future rollovers self-manage again.

The trap is that nothing fails loudly: the template update succeeds, the rollover succeeds, and the write path keeps serving. Only the lifecycle silently stops for new indices, so you discover it days later when a hot node fills. Detaching a policy at creation time is a Phase Transition Logic failure — the transitions never start — and re-attaching a whole generation of indices at once is exactly the job of Bulk Policy Attachment. Getting the template’s rollover wiring right in the first place is covered under Rollover Trigger Configuration.

How a template edit detaches ISM from newly rolled indices, and the two-arm recovery A horizontal timeline of backing indices logs-000001 through logs-000005. Indices one to three predate a template edit and are managed, each carrying a policy_id. A template edit event drops plugins.index_state_management.policy_id. Indices four and five, created by later rollovers, come up with a null policy_id and are unmanaged, never transitioning. Recovery arm one: detect via _plugins/_ism/explain filtered for null policy_id, then POST _plugins/_ism/add to re-attach the policy to indices four and five. Recovery arm two: repair the template by restoring policy_id or the policy ism_template so the next rollover self-attaches. template edit drops policy_id logs-000001 managed logs-000002 managed logs-000003 managed logs-000004 unmanaged logs-000005 unmanaged Arm 1 · detect & re-attach explain filter null → _ism/add recovers existing indices Arm 2 · repair the template restore policy_id / ism_template fixes all future rollovers

Prerequisites

Confirm each item before re-attaching — a bulk _ism/add against indices that already have a policy silently no-ops, and re-attaching without fixing the template just recreates the problem on the next roll.

Step-by-step procedure

Detect the full set of unmanaged indices first, re-attach them, then fix the template so the leak stops — doing the template fix alone leaves the already-created indices stranded forever.

1. Detect unmanaged indices in the series

_plugins/_ism/explain reports a policy_id of null for any index ISM is not managing. Query the whole pattern and let the null values reveal the detached generation.

HTTP
GET _plugins/_ism/explain/logs-*?pretty
Text
{
  "logs-000003": { "index.plugins.index_state_management.policy_id": "logs-hot-warm-cold", "enabled": true },
  "logs-000004": { "index.plugins.index_state_management.policy_id": null, "enabled": null },
  "logs-000005": { "index.plugins.index_state_management.policy_id": null, "enabled": null },
  "total_managed_indices": 3
}

Gotcha: an unmanaged index shows policy_id: null and enabled: null, not enabled: false — a false means the policy is attached but paused, which is a different problem. Note that total_managed_indices counts only the managed ones, so a count lower than your index total is the fastest smoke test that something detached.

2. Script the detection so you catch every detached index

For anything past a couple of indices, filter programmatically. This opensearch-py snippet lists exactly the indices that need re-attaching, ignoring the integer summary key.

Python
from opensearchpy import OpenSearch

client = OpenSearch("https://os.internal:9200", http_auth=("svc_ism", "..."), verify_certs=True)

explain = client.transport.perform_request("GET", "/_plugins/_ism/explain/logs-*")
unmanaged = [
    idx for idx, meta in explain.items()
    if isinstance(meta, dict)
    and meta.get("index.plugins.index_state_management.policy_id") is None
]
print(f"{len(unmanaged)} unmanaged indices: {unmanaged}")
Text
2 unmanaged indices: ['logs-000004', 'logs-000005']

Gotcha: guard with isinstance(meta, dict)total_managed_indices is an integer sitting alongside the index keys, and calling .get() on it raises AttributeError. This is the same key-filtering discipline the bulk workflow relies on.

3. Re-attach the policy with _plugins/_ism/add

Attach the policy to the detached indices. _ism/add accepts a comma-separated list or the pattern, and it only affects indices that currently have no policy — already-managed indices are left untouched.

HTTP
POST _plugins/_ism/add/logs-000004,logs-000005
{
  "policy_id": "logs-hot-warm-cold"
}
Text
{
  "updated_indices": 2,
  "failures": false,
  "failed_indices": []
}

Gotcha: _ism/add starts the index at the policy’s default_state, not at whatever state a same-age managed sibling is in — a week-old index that should already be warm will re-enter at hot and re-run those transitions. If that matters, drive it forward with POST _plugins/_ism/change_policy and an explicit target state instead.

4. Find the template edit that dropped the policy setting

The detached indices are only a symptom; the template is the leak. Inspect the composable template backing the pattern and confirm whether the ISM setting is present.

HTTP
GET _index_template/logs-template?filter_path=index_templates.index_template.template.settings
Text
{
  "index_templates": [
    { "index_template": { "template": { "settings": { "index": {
      "number_of_shards": "3",
      "number_of_replicas": "1"
    } } } } }
  ]
}

Gotcha: the absence of any plugins.index_state_management.policy_id under settings.index is the smoking gun — an earlier edit replaced the whole settings block and dropped it. If you instead rely on the policy’s ism_template for auto-attach, the setting legitimately won’t be here; check the policy’s ism_template.index_patterns still matches logs-* in that case.

5. Repair the template so future rollovers self-manage

Put the ISM setting back into the template (or fix the policy’s ism_template pattern). This does not touch existing indices — it only governs indices created from the next rollover onward.

HTTP
PUT _index_template/logs-template
{
  "index_patterns": ["logs-*"],
  "template": {
    "settings": {
      "index.number_of_shards": 3,
      "index.number_of_replicas": 1,
      "plugins.index_state_management.policy_id": "logs-hot-warm-cold",
      "plugins.index_state_management.rollover_alias": "logs-write"
    }
  },
  "priority": 200
}
Text
{ "acknowledged": true }

Gotcha: re-adding policy_id also requires the rollover_alias setting to survive the edit, or the re-managed index rolls but the new backing index is unmanaged again — the two settings must travel together. Bump priority above any conflicting template so yours wins the match.

Verification

Confirm every index is managed and the next-created index inherits the policy.

Shell
# 1. Are the previously detached indices now managed?
curl -s "https://os.internal:9200/_plugins/_ism/explain/logs-000004?pretty"
# "index.plugins.index_state_management.policy_id": "logs-hot-warm-cold", "enabled": true
Shell
# 2. Does the total managed count now equal the index count?
curl -s "https://os.internal:9200/_plugins/_ism/explain/logs-*?filter_path=total_managed_indices"
# { "total_managed_indices": 5 }
Shell
# 3. Does a freshly rolled index inherit the policy from the fixed template?
curl -s -XPOST "https://os.internal:9200/logs-write/_rollover" >/dev/null
curl -s "https://os.internal:9200/_plugins/_ism/explain/logs-000006?filter_path=**.policy_id"
# "index.plugins.index_state_management.policy_id": "logs-hot-warm-cold"

A healthy result shows total_managed_indices equal to the number of backing indices, every index reporting a non-null policy_id, and a newly rolled logs-000006 inheriting the policy automatically. If the freshly rolled index still comes up null, the template fix did not take — recheck priority and that the pattern matches.

Common failures

Symptom Root cause Fix command
New indices come up policy_id: null after a roll Template edit dropped plugins.index_state_management.policy_id Restore the setting in PUT _index_template/logs-template
_ism/add returns failures: true for some indices Those indices already have a policy attached Filter to null-policy_id indices first; add no-ops on managed ones
Re-attached index re-runs hot transitions _ism/add starts at default_state, not the age-appropriate state Use POST _plugins/_ism/change_policy with an explicit state
Template fixed but rollovers still unmanaged A higher-priority template without the setting wins the match Raise this template’s priority or fix the conflicting one
Re-managed index rolls into an unmanaged child rollover_alias was dropped alongside policy_id Add both policy_id and rollover_alias back together in the template

Frequently asked questions

Why did only the newest indices lose their policy, not all of them?

ISM attaches a policy at index creation, driven by the template’s policy_id setting (or the policy’s ism_template). Indices created before the template edit already carry the attachment on their own settings and keep it; only indices created after the edit inherit the now-missing setting and come up unmanaged. That is why the detachment appears as a clean cutoff at the template-edit boundary.

Does fixing the template re-attach the already-detached indices?

No. A template only governs indices created after it is applied — it never reaches back to existing indices. You must both repair the template (to stop the leak) and run _plugins/_ism/add against the already-detached indices (to recover them). Doing only the template fix leaves the stranded generation unmanaged forever.

Should I use the template `policy_id` setting or the policy's `ism_template` block?

Both work; pick one and be consistent. The template policy_id setting is explicit and easy to audit in _index_template, but it is the setting most often lost in a template rewrite. The policy’s ism_template centralizes the pattern match in the policy itself and survives index-template edits, at the cost of the auto-attach logic being less obvious to someone reading the index template.

Up: Phase Transition Logic