Repository: dbt-labs/dbt-core (python)

Issue:
[Bug] On-run-end schemas does not seem to apply to functions schema

### Is this a new bug in dbt-core?

- [x] I believe this is a new bug in dbt-core
- [x] I have searched the existing issues, and I could not find an existing issue for this bug

### Current Behavior

I am trying to access all schemas on-run-end and change the 'owner' but the new schema under functions does not appear

I am doing the following in my dbt_project.yml ;
```
on-run-end:
  - "{% for schema in schemas %} {% do run_query('alter schema ' ~ schema ~ ' set owner to data_engineers') %} {% endfor %}"

```
However, the new schema under functions does not appear in on the run end and the schema owner does not get altered. I have to do specifically;
```
on-run-end:
  - >
      {% set all_schemas = (schemas | list) %}
      {% do all_schemas.append(target.schema ~ '_functions') %}
      {% for schema in all_schemas %}
        {% do run_query('alter schema `' ~ target.catalog ~ '`.`' ~ schema ~ '` owner to `data_engineers`') %}
      {% endfor %}
```

### Expected Behavior

I expect the new functions schema to also appear when I call schemas under on-run-end

### Steps To Reproduce

```
functions:
  +schema: functions

on-run-end:
  - "{% for schema in schemas %} {% do run_query('alter schema ' ~ schema ~ ' set owner to data_engineers') %} {% endfor %}"
  - '{{ log_dbt_test_results(results) }}'

```
Does not change the owner of the functions schema.

### Relevant log output

```shell

```

### Environment

```markdown
- OS:
- Python:
- dbt:
```

### Which database adapter are you using with dbt?

Databricks

### Additional Context

_No response_


Patch:
diff --git a/.changes/unreleased/Fixes-20260324-194846.yaml b/.changes/unreleased/Fixes-20260324-194846.yaml
new file mode 100644
index 00000000000..837a856dc07
--- /dev/null
+++ b/.changes/unreleased/Fixes-20260324-194846.yaml
@@ -0,0 +1,6 @@
+kind: Fixes
+body: 'Fixed a bug where UDF function schema was not available in jinja context of on-run-end hook '
+time: 2026-03-24T19:48:46.21743+05:30
+custom:
+    Author: sriramr98
+    Issue: "12516"
diff --git a/core/dbt/contracts/graph/nodes.py b/core/dbt/contracts/graph/nodes.py
index 323454e4776..225b7a3b3c8 100644
--- a/core/dbt/contracts/graph/nodes.py
+++ b/core/dbt/contracts/graph/nodes.py
@@ -1626,6 +1626,10 @@ def to_logging_dict(self) -> Dict[str, Union[str, Dict[str, str]]]:
 @dataclass
 class FunctionNode(CompiledNode, FunctionResource):
 
+    @property
+    def is_relational(self):
+        return True
+
     @classmethod
     def resource_class(cls) -> Type[FunctionResource]:
         return FunctionResource


Test patch:
diff --git a/tests/functional/functions/test_udfs.py b/tests/functional/functions/test_udfs.py
index 21645c6fb6d..86c909992fd 100644
--- a/tests/functional/functions/test_udfs.py
+++ b/tests/functional/functions/test_udfs.py
@@ -647,3 +647,59 @@ def test_functions_get_schema_created_if_necessary(self, project):
         function_node = result.results[0].node
         assert isinstance(function_node, FunctionNode)
         assert "alt_function_schema_" in function_node.schema
+
+
+model_sql = """select 1 as id"""
+
+
+class TestFunctionSchemasInOnRunEnd:
+    """Test that function schemas appear in the on-run-end `schemas` variable.
+
+    Regression test for https://github.com/dbt-labs/dbt-core/issues/12516
+    """
+
+    @pytest.fixture(scope="class")
+    def functions(self) -> Dict[str, str]:
+        return {
+            "double_it.sql": double_it_sql,
+            "double_it.yml": double_it_yml,
+        }
+
+    @pytest.fixture(scope="class")
+    def models(self) -> Dict[str, str]:
+        return {"my_model.sql": model_sql}
+
+    @pytest.fixture(scope="class")
+    def project_config_update(self):
+        return {
+            "functions": {"+schema": "udfs"},
+            "on-run-end": [
+                "create table if not exists {{ target.schema }}.on_run_end_schemas (schema_name text)",
+                "insert into {{ target.schema }}.on_run_end_schemas (schema_name) values "
+                "{% for schema in schemas %}('{{ schema }}'){% if not loop.last %},{% endif %}{% endfor %}",
+            ],
+        }
+
+    @pytest.fixture(scope="function")
+    def setUp(self, project):
+        project.run_sql(f"drop table if exists {project.test_schema}.on_run_end_schemas")
+
+    def test_function_schema_in_on_run_end_schemas(self, setUp, project):
+        run_dbt(["build"])
+
+        results = project.run_sql(
+            f"select schema_name from {project.test_schema}.on_run_end_schemas",
+            fetch="all",
+        )
+        schemas = {r[0] for r in results}
+
+        # The model's schema (target schema) should be present
+        assert (
+            project.test_schema in schemas
+        ), f"Expected model schema '{project.test_schema}' in schemas, got {schemas}"
+
+        # The function's custom schema should also be present
+        expected_fn_schema = f"{project.test_schema}_udfs"
+        assert (
+            expected_fn_schema in schemas
+        ), f"Expected function schema '{expected_fn_schema}' in schemas, got {schemas}"
diff --git a/tests/unit/contracts/graph/test_udfs.py b/tests/unit/contracts/graph/test_udfs.py
index 56c2987400e..a4ddccba96e 100644
--- a/tests/unit/contracts/graph/test_udfs.py
+++ b/tests/unit/contracts/graph/test_udfs.py
@@ -80,3 +80,15 @@ def test_function_node_description_with_default_database(
     )
 
     assert runner.describe_node() == "function schema.name"
+
+
+def test_function_node_is_relational(function_node):
+    """FunctionNode.is_relational should return True so that function schemas
+    are included in the on-run-end `schemas` variable (dbt-core#12516)."""
+    assert function_node.is_relational is True
+
+
+def test_function_node_is_not_refable(function_node):
+    """Functions are not refable (they use {{ function() }} not {{ ref() }}),
+    so is_refable should remain False even though is_relational is True."""
+    assert function_node.is_refable is False