> ## Content Index
> Fetch the complete content index at: https://www.sfrt.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# Snowflake gave everyone 102 models and didn't tell me
- URL: https://www.sfrt.io/snowflake-gave-everyone-102-models-and-didnt-tell-me/
- Published: 2026-09-04T13:19:19.000Z
- Updated: 2026-09-04T13:19:19.000Z
- Description: 200 users, zero curation, and a bootstrap grant I didn’t ask for.
- Author: Martin Seifert
- Tags: Lakehouse

TL/DR: ❄️ Snowflake is moving away from `CORTEX_MODELS_ALLOWLIST` toward model RBAC. When I checked if that is of any relevance to my account, the bootstrap already happened in the background: all 102 models currently in `SNOWFLAKE.MODELS` were reachable by my \~200 active users, with no heads-up when model 103 arrives. So I built a monitor first and the lockdown second.

---

BCR-2378 was the trigger: Snowflake is deprecating the allowlist parameter and switching to role-based access control for models. Starting 2026-09-08 with full retirement on 2026-11-18.

[CORTEX\_MODELS\_ALLOWLIST deprecation and embedding model RBAC enforcement (Pending) | Snowflake Documentation![](https://storage.ghost.io/c/7d/94/7d942fe1-7868-4a1a-b2c9-4eb415b1a546/content/images/icon/favicon-ae008b15-243c-4825-9280-8352d2dd2a9f.png)Snowflake Documentation](https://docs.snowflake.com/en/release-notes/bcr-bundles/un-bundled/bcr-2378?ref=sfrt.io)

## Nothing breaks... which might be a problem

First check: what is my account's allowlist currently set to? `CORTEX_MODELS_ALLOWLIST = 'All'`, the default, never explicitly modified by me. Second check: `SHOW GRANTS TO APPLICATION ROLE SNOWFLAKE.PUBLIC`. That's where it got interesting: Snowflake had already applied its own compatibility bootstrap, granting `SNOWFLAKE."CORTEX-MODEL-ROLE-ALL"` to `SNOWFLAKE.PUBLIC`. Nothing breaks on enforcement day. My account is already RBAC-equivalent to its previous allowlist behavior 👍

[Privileges and model access for Cortex AI Functions | Snowflake Documentation![](https://storage.ghost.io/c/7d/94/7d942fe1-7868-4a1a-b2c9-4eb415b1a546/content/images/icon/favicon-13658ce7-aa52-47b1-81e2-17383d588904.png)Snowflake Documentation](https://docs.snowflake.com/en/user-guide/snowflake-cortex/aisql-privileges-and-access?ref=sfrt.io#migrate-from-the-allowlist-to-rbac)

But "nothing breaks" isn't the same as "nothing to do". That bootstrap grant reminded me of something I pushed ahead for weeks: every model Snowflake has shipped, the full Claude/GPT/Grok/Gemini families included, is callable by any of my \~200 active users today. And that could be rather expensive really quickly 👎

Curating that grant down to a named model list is the goal. But first, I need a monitor as prerequisite: I can't write a curated list without knowing what exists, and Snowflake (currently) doesn't really have a channel to tell me when models become available to me. So how do I ever update this curated list? 🤔

## Existing is not the same as callable

Two facts made a one-time cleanup insufficient. `SHOW MODELS IN SNOWFLAKE.MODELS` refreshes roughly daily on its own... but a model appearing there doesn't really say much about whether my account can actually use it. Since I only want to use models available in the EU, cross-region inference is enabled for exactly two region groups:

`alter account set cortex_enabled_cross_region = 'AWS_EU,AZURE_EU';`

Therefore, a model existing globally says nothing about whether it's callable from my region under that setting. And this is why Snowflake currently doesn't really have a useful channel to tell me when new models show up - I may read on LinkedIn when a new frontier model arrives in US regions, but nobody informs me when it arrives in the EU 😅 The only way to know, short of reading Snowflake's internal region-rollout schedule (which I don't have) is to actually try to call a model.

I started setting up my monitor with a snapshot table carrying two facts: what model showed up when and whether it answered from my region (under my account-level settings):

```sql
create or alter table meta.cortex.t_model
(
      model_name        varchar         not null primary key
    , model_type        varchar
    , created_on        timestamp_ntz
    , first_seen        timestamp_ntz   default current_timestamp()
    , last_seen         timestamp_ntz
    , eu_available      boolean         comment 'result of a 1-token AI_COMPLETE probe under CORTEX_ENABLED_CROSS_REGION; null = not a chat model, not probed'
    , eu_probe_error    varchar         comment 'AI_COMPLETE error text if the probe failed, or "skipped: ..." if the model type is not probable'
    , notified          timestamp_ntz   comment 'when this row was last included in a cortex_models_new notification'
)
comment = 'Snapshot of SNOWFLAKE.MODELS, used to detect and alert on newly available Cortex models'
;

```

`notified` does two things here: it marks a row as already reported and it gets cleared back to null whenever a previously failing model flips to available (we'll get to that in a bit).

## The procedure

Then I (well, not me personally, of course! 😅) frequently refresh the list of current models and probe those that need to be probed:

```sql
create or replace procedure meta.cortex.p_refresh_model_inventory()
copy grants
returns varchar
language sql
comment = 'Snapshot SHOW MODELS IN SNOWFLAKE.MODELS into T_MODEL and probe newly-seen chat models for EU availability'
as
$$
declare
    model_count       integer default 0;
    probe_result      varchar;
    v_model_name      varchar;
    c_unprobed cursor for
        select model_name, model_type
        from meta.cortex.t_model
        where (eu_available is null and eu_probe_error is null)                       -- never probed
            or (eu_available = false and dayofweek(current_date()) = 1)               -- previously failed, re-check Mondays only
    ;
begin
    show models in snowflake.models;

    create or replace temporary table meta.cortex.t_current_models as
        select
              "name"          as model_name
            , "model_type"    as model_type
            , "created_on"::timestamp_ntz as created_on
        from table(result_scan(last_query_id(-1)))
    ;

    model_count := (select count(*) from meta.cortex.t_current_models);

    merge into meta.cortex.t_model tgt
    using meta.cortex.t_current_models src
        on tgt.model_name = src.model_name
    when matched then
        update set tgt.last_seen = current_timestamp()
    when not matched then
        insert (model_name, model_type, created_on, first_seen, last_seen)
        values (src.model_name, src.model_type, src.created_on, current_timestamp(), current_timestamp())
    ;

    -- probe eu availability for models not yet probed, one AI_COMPLETE call per model (1 token)
    for rec in c_unprobed do
        v_model_name := rec.model_name;

        if (rec.model_type != 'CORTEX_BASE'
            or v_model_name ilike any ('%EMBED%', '%EXTRACT%', '%PARSE%', '%TRANSCRIBE%', '%SENTIMENT%', '%TRANSLATE%', '%GUARD%', '%TEXT2SQL%')
        ) then
            update meta.cortex.t_model
            set eu_probe_error = 'skipped: not an AI_COMPLETE-callable chat model'
            where model_name = :v_model_name
            ;
            continue;
        end if;

        begin
            select ai_complete(:v_model_name, 'ping', {'max_tokens': 1}) into :probe_result;
            update meta.cortex.t_model
            set eu_available = true
                , eu_probe_error = null
                , notified = null   -- un-notify: a model that just became EU-available is worth reporting again
            where model_name = :v_model_name
            ;
        exception
            when other then
                update meta.cortex.t_model
                set eu_available = false
                    , eu_probe_error = left(:sqlerrm, 500)
                where model_name = :v_model_name
                ;
        end;
    end for;

    return 'refreshed ' || model_count || ' models';
end;
$$
;

```

The `SHOW ... → temp table via RESULT_SCAN(LAST_QUERY_ID(-1)) → MERGE` shape is necessary here as there is no other way to get the current list of models programmatically than `SHOW MODELS`, but this can't sit inside a CTE. And the type/name filter before probing skips embeddings, extraction, translation, guard, and text2sql models... none of them take an `AI_COMPLETE` chat-style call, so probing them would just burn credits on a guaranteed error.

## Probing 67 models every day?

The first working version of the re-probe (after the initial probe of all models) cursor was:

```sql
where (eu_available is null and eu_probe_error is null)
    or eu_available = false

```

67 previously failed models (usually account-blocked models) got re-probed cleanly. Unfortunately, that would also mean re-probing all non-EU models every day. Its cost is fairly small per call (1-token `AI_COMPLETE` ping), but daily... trying to raise region rollout of new models to my attention, that doesn't happen daily.

I fixed it by only doing the full re-probe once a week: `or (eu_available = false and dayofweek(current_date()) = 1)`. Running the procedure on a Friday (`dayofweek` \= 5) results in a run duration of 9.5 seconds, so zero probes fired... vs 3-4 minutes when the cursor included the 67 failing rows.

## Wired into what already exists

The view feeding the notification (to Teams in my case) only ever returns rows not yet reported:

```sql
create or replace view consume.monitor.v_cortex_models_new
comment = 'Cortex models newly seen in SNOWFLAKE.MODELS, not yet reported to Teams'
as
select
      m.model_name
    , m.first_seen
    , m.eu_available
from meta.cortex.t_model m
where m.notified is null
order by m.first_seen desc
;

```

A daily task calls the refresh procedure and a generic Teams-alert procedure I already had. This procedure posts an Adaptive Card if the view's row count is not zero:

```sql
create or alter task meta.cortex.ta_model_monitor
user_task_managed_initial_warehouse_size = 'XSMALL'
serverless_task_max_statement_size = 'XSMALL'
schedule = 'USING CRON 15 7 * * 1-5 Europe/Zurich'
target_completion_interval = '30 MINUTES'
user_task_timeout_ms = 1800000 -- 30 minutes
allow_overlapping_execution = false
suspend_task_after_num_failures = 10
task_auto_retry_attempts = 1
comment = 'Detect new Cortex models, alert to Teams'
query_tag = '{"task":{"database":"META","schema":"CORTEX","task_name":"TA_MODEL_MONITOR"}}'
as
begin
    call meta.cortex.p_refresh_model_inventory();
    call meta.load.p_monitor('cortex_models_new'); -- this sends the notification to Teams, but that could also be any other channel
    update meta.cortex.t_model set notified = current_timestamp() where notified is null;
end;
;
alter task meta.cortex.ta_model_monitor resume;
```

Now the curated [RBAC grant](https://docs.snowflake.com/en/user-guide/snowflake-cortex/aisql-privileges-and-access?ref=sfrt.io#migrate-from-the-allowlist-to-rbac) itself, the actual point of this whole effort, is easy enough: the monitor tells me what models I can grant users access to and I'll be sooo ready as soon as an EU-hosted version of DeepSeek V4 Flash becomes available 😎