Snowflake gave everyone 102 models and didn't tell me
200 users, zero curation, and a bootstrap grant I didn’t ask for.
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.
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 👍
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):
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:
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:
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:
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:
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 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 😎

