# Errors in script to find orphaned tags and recursion length

**URL:** <https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713>\
**Category:** Ignition\
**Tags:** ignition81, scripting\
**Created:** [October 24, 2025, 11:11pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713 "2025-10-24T23:11:32Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 24, 2025, 11:11pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/1 "2025-10-24T23:11:32Z")

</div>

I am trying to build a script to find orphaned tags, and am getting errors relating to `QualifiedPath` and `cannot coerce value "histprov:prov_name" into type class java.lang.string.`

This is the code I have at the moment:

```python
from java.util import Date

# Parameters
days_without_data = 30 # Threshold for inactivity
cutoff_time = system.date.addDays(system.date.now(), -days_without_data)

def collectHistoricalTagPaths(provider, parentPath=""):
    tag_paths = []
    results = system.tag.browseHistoricalTags(provider, parentPath).getResults()
    for result in results:
        if result.hasChildren():
            tag_paths.extend(collectHistoricalTagPaths(provider, result.getPath()))
        else:
			tag_path = "[{}]{}".format(provider, result.getPath())
			tag_paths.append(tag_path)
    return tag_paths
    
orphaned_tags = []
providers = system.tag.browseHistoricalTags()
provider_results = providers.getResults()

for provider_result in provider_results:
    # Convert QualifiedPath to string and extract provider name
    full_path_str = str(provider_result.getPath()) # e.g., 'histprov:prov_name:/drv:...'
    if full_path_str.startswith("histprov:"):
        provider_name = full_path_str.split(":")[1].split("/")[0] # Extract 'prov_name'
        tag_paths = collectHistoricalTagPaths(provider_name)
        for tag_path in tag_paths:
            try:
                history = system.tag.queryTagHistory(
                    paths=[tag_path], # Must be a list of strings
                    startDate=cutoff_time,
                    endDate=system.date.now(),
                    returnSize=1,
                    aggregationMode="LastValue"
                )
                if history.getRowCount() == 0:
                    orphaned_tags.append(tag_path)
            except Exception as e:
                orphaned_tags.append(tag_path)

```

Which throws the `java.lang.ClassCastException` error for coercing the value into a string, specifically on the line that calls the function: `collectHistoricalTagPaths`.

I know that the `provider_name` that is passed into the function is simply the name of the historical provider. One example from the list is:`Sample_SQLite_Database`.

I think the Exception is actually thrown on the line:  
`tag_paths.extend(collectHistoricalTagPaths(provider, result.getPath()))`

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [October 24, 2025, 11:59pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/2 "2025-10-24T23:59:06Z")

</div>

> [@mdemaris](#):
>
> `tag_paths.extend(collectHistoricalTagPaths(provider, result.getPath()))`

You're passing the raw underlying Java type in to the recursive execution of `collectHistoricalTagPaths` - which then gets passed to `browseHistoricalTags`, but that function only accepts a real string, at this line:  
`results = system.tag.browseHistoricalTags(provider, parentPath).getResults()`

The simplest fix would be to ensure the path is a string before you start your recursion:  
`tag_paths.extend(collectHistoricalTagPaths(provider, str(result.getPath())))`

---

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 25, 2025, 12:10am UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/3 "2025-10-25T00:10:15Z")

</div>

Aha! I was looking at the wrong parameter. Fixed that, thank you. Now I get a maximum recursion length error 😂

I added a maximum depth to the recursion:

```python
days_without_data = 30 # Threshold for inactivity
cutoff_time = system.date.addDays(system.date.now(), - days_without_data)
max_depth = 4

def collectHistoricalTagPaths(provider, parentPath="", depth=0):
	count = 0
	tag_paths = []
	if depth > max_depth:
		return tag_paths
		
	results = system.tag.browseHistoricalTags(provider, parentPath).getResults()
	for result in results:
# count += 1
# print(count)
		child_path = str(result.getPath())
		if result.hasChildren():
			tag_paths.extend(collectHistoricalTagPaths(provider, child_path, depth + 1))
		else:
			tag_path = "[{}]{}".format(provider, child_path)
			tag_paths.append(tag_path)
	
	return tag_paths

```

If `max_depth` is 5, it seems to go on forever. At 1, 2 and 3, I get no results for orphaned tags. currently running with depth at 4, which seems to take about 1 minute per Historical Tag Provider. TBH, I am not even sure I am asking the `system.tag.browse...` the correct question.

---

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 25, 2025, 12:56am UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/4 "2025-10-25T00:56:08Z")

</div>

Well... No results with a depth of 4. Hm... Maybe there are no orphaned tags? Probably have to poke around the DB tables or something.

---

<div class="post-metadata">

**Author:** ![pascal.fragnoud](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/pascal.fragnoud/32/24728_2.png) [@pascal.fragnoud](https://forum.inductiveautomation.com/u/pascal.fragnoud)\
**Post date:** [October 25, 2025, 8:30am UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/5 "2025-10-25T08:30:14Z")

</div>

If you’re running into recursion depth errors, you can drop the recursion and maintain a stack instead.  
Start by adding the root to the stack, then loop on `while stack` so you’re processing items as long as the stack is not empty.

In that loop, pop the stack, process the item, and put its children on the stack.

This pretty much does what recursion does, but without its issues.

PS: I didn’t read your code, I’m on mobile and I dislike reading code on mobile.

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [October 25, 2025, 1:44pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/6 "2025-10-25T13:44:22Z")

</div>

I just select tag paths from sqlth\_te where retired is null into a list.

Then chunk reading historyEnabled with a system.tag.readBlocking(tpaths)

Then you can just filter or traverse the results looking for tags with historyEnabled False.

Build up a list of those and after that I just select top 1 retired from the partition and write it to the retired field of the False tags.

---

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 27, 2025, 6:10pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/7 "2025-10-27T18:10:24Z")

</div>

> [@MMaynard](#):
>
> select top 1 retired from the partition and write it to the retired field of the False tags.

What do you mean?

---

<div class="post-metadata">

**Author:** ![MMaynard](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mmaynard/32/3366_2.png) [@MMaynard](https://forum.inductiveautomation.com/u/MMaynard)\
**Post date:** [October 27, 2025, 8:50pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/8 "2025-10-27T20:50:13Z")

</div>

To set the orphaned tags as retired, you can just select the top retired value from the table and set the tags record you want to retire to that value.

Or you can get the timestamp from the system and use that.

I just script it all on buttons with tables on a window.

Run it every few months just to check on things.

Especially on DEV gateways where we are moving thousands of tags and setting/resetting history enabled on them.

`SELECT TOP (1) [retired] FROM [sqlth_te] ORDER BY [retired] DESC`

---

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 27, 2025, 9:12pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/9 "2025-10-27T21:12:05Z")

</div>

Ah, ok. Just wanted to make sure I understood.

So, I am going through the list of Historical Tag Providers using the `.browseHistoricalTags()` and for many of them I get `Unknown Provider Name`. Why would this be?

**EDIT**  
Also, while playing around with `system.tag.browse`, I am getting no results. Our Tag Provider name is `default`, so it seems that this: `results = system.tag.browse('[default]').getResults()` should give me something?? But I get a zero length list.

---

<div class="post-metadata">

**Author:** ![mdemaris](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/mdemaris/32/54639_2.png) [@mdemaris](https://forum.inductiveautomation.com/u/mdemaris)\
**Post date:** [October 27, 2025, 9:59pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/10 "2025-10-27T21:59:54Z")

</div>

> [@MMaynard](#):
>
> Then chunk reading historyEnabled with a system.tag.readBlocking(tpaths)

Where is there a way to read `historyEnabled`? I haven't found anything in the docs about filtering on this property, yet.

---

<div class="post-metadata">

**Author:** ![paul-griffith](https://sea1.discourse-cdn.com/inductiveautomation/user_avatar/forum.inductiveautomation.com/paul-griffith/32/37249_2.png) [@paul-griffith](https://forum.inductiveautomation.com/u/paul-griffith)\
**Post date:** [October 27, 2025, 10:07pm UTC](https://forum.inductiveautomation.com/t/errors-in-script-to-find-orphaned-tags-and-recursion-length/111713/11 "2025-10-27T22:07:38Z")

</div>

You can read ~any property of a tag by just concatenating the 'raw' name of the property with the path.

That is:

- `system.tag.readBlocking(["path/to/tag"])` -\> a QualifiedValue containing the tag's value
- `system.tag.readBlocking(["path/to/tag.value"])` -\> a QualifiedValue containing the tag's value
- `system.tag.readBlocking(["path/to/tag.someOtherProperty"])` -\> a QualifiedValue containing `someOtherProperty`'s value on the tag
- `system.tag.readBlocking(["path/to/tag.historyEnabled"])` -\> a QualifiedValue containing the value of the `historyEnabled` property
