Read this first if your upkeep stopped executing
Chainlink Automation v1.x sunset on June 30, 2026. Chainlink Automation v2.1 sunset on July 31, 2026, four days ago.If you had a production upkeep running on Automation and haven't migrated yet, it has already stopped being performed. The Chainlink Automation App will show a "deprecated" notice for any upkeep on a registry earlier than v2.1, and as of the v2.1 sunset date, that now includes v2.1 itself.
The replacement is the Chainlink Runtime Environment (CRE), and Chainlink Labs built a specific migration path, called the Automation Migration template, precisely so this transition doesn't require rewriting your existing contracts from scratch. This article is the fastest path from "my upkeep stopped running" to "I have a working CRE workflow," using the official bridge pattern.
This is day 22 of the 28-day Chainlink architecture series, but today isn't theory. It's a checklist, and if you're reading this because something in production broke, start executing it now.
What actually changes, in plain terms
In Automation, you registered an upkeep with two functions: checkUpkeep(), simulated off-chain by the Automation network, and performUpkeep(), executed on-chain when checkUpkeep() returned true.
In CRE, the equivalent unit is a workflow: a TypeScript or Go project compiled to WebAssembly and registered with the network. Workflows are started by triggers (a cron schedule, an HTTP request, or an on-chain log event), run your logic off-chain with no gas constraints during the check phase, and write results on-chain through a signed report delivered via the CRE KeystoneForwarder to any contract implementing IReceiver.
CRE is a strict superset of Automation. Everything Automation does, CRE does, and several patterns that used to require multiple separate upkeeps now collapse into a single workflow.
Here's the direct terminology mapping so you're not translating concepts on the fly:
| Chainlink Automation | Chainlink CRE |
|---|---|
| Upkeep registration | CRE workflow deployment |
| Upkeep contract | Existing target contract + an IReceiver bridge |
checkUpkeep() function |
Workflow logic inside the handler |
performUpkeep(bytes performData) |
onReport(metadata, report) on an IReceiver, then a bridge call to your target contract |
| Time-based Upkeep | Built-in Cron trigger |
| Log Trigger Upkeep | EVM Log trigger |
| Custom Logic Upkeep | Cron trigger + evmClient.callContract() in the handler |
| Automation Forwarder | CRE KeystoneForwarder + your receiver authorization |
The fastest path: the Bridge pattern
Here's the part that matters most if you're migrating today under time pressure. You do not need to reimplement checkUpkeep, checkLog, or performUpkeep inside your existing contract. The official Automation Migration template uses a Bridge pattern: you deploy a generic AutomationReceiver.sol contract that receives CRE reports and forwards approved calls to your existing Automation contract, unchanged.
The only thing you may need to touch in your existing contract is a permission check. If your contract currently checks msg.sender against an Automation Forwarder allowlist, or has role-based permissions gating who can call performUpkeep, you need to authorize the new AutomationReceiver address or adjust that permission boundary. Otherwise, your business logic stays exactly as it is.
Migration steps, in order
Step 1: Scaffold the migration project
cre init --template=automation-migration-go --project-name my-automation-migration --workflow-name my-workflow
This pulls the official template directly, available in both Go and TypeScript. Use whichever matches your team's existing stack.
Step 2: Deploy the Bridge contract
Deploy AutomationReceiver.sol from the template to your target chain, passing your chain's CRE KeystoneForwarder address to the constructor. The forwarder address is chain-specific. Check the Forwarder Directory in the CRE docs for the correct address before deploying; passing the wrong forwarder address means your receiver will never accept valid reports.
Step 3: Configure your workflow
Update my-workflow/config.test.json with your previously deployed AutomationReceiver address, your target contract address, the migration type (CRON, CUSTOM, or LOG depending on which upkeep type you're migrating), and the schedule or log filters that match your original upkeep configuration.
Step 4: Authorize the call
This is the step people miss and then wonder why their workflow fails silently. Before your workflow can actually call your target contract through the receiver, you need to run a setCallAllowed() transaction:
Function: setCallAllowed(address,bytes4,bool)
target: <your existing Automation upkeep contract address>
selector: <the 4-byte function selector for the function being called>
allowed: true
Compute the function selector with the cast CLI tool:
cast sig 'performUpkeep(bytes)'
# Output: 0x4585e33b
If you're calling a custom function instead of performUpkeep, compute the selector for that function specifically. This step is what tells the AutomationReceiver it's allowed to forward calls to your specific target and function. Skip it, and every execution reverts with CallNotAllowed.
Step 5 (production, don't skip this): set workflow identity checks
The generic receiver, as configured after step 4, will forward calls from any workflow that references your target contract and approved selector. For a migration test, that's fine. For production, it's a real security gap: anyone who deploys a CRE workflow calling your AutomationReceiver with the right target and selector could trigger your contract's function.
Lock this down with the identity setters:
setExpectedAuthor(address _author)
setExpectedWorkflowId(string _workflowId)
setExpectedWorkflowName(string _workflowName)
Set at least one of these before you consider the migration production-ready. A generic receiver accepting arbitrary (target, data) calls from any workflow is convenient for testing and a genuine vulnerability if left open in production.
Step 6: Simulate before deploying
cre workflow simulate my-workflow --target=test-settings
For log-trigger migrations specifically, provide a transaction hash containing the actual event so the simulator doesn't sit waiting for a live event to fire:
cre workflow simulate my-workflow \
--target=test-settings \
--non-interactive \
--trigger-index=0 \
--evm-tx-hash=0x... \
--evm-event-index=0
Step 7: Deploy to production
cre workflow deploy my-workflow --target=production-settings
If your workflow fails with CallNotAllowed
This is the most common failure during migration. Four things to check in order:
Function selector mismatch.The selector configured in
setCallAllowed()must exactly match the function your workflow is actually calling. Recompute it withcast sigand compare byte for byte.Permission not actually set. Confirm
setCallAllowed()was called withallowed: truefor the specific target and selector pair you're using. A transaction that reverted or was sent with the wrong parameters leaves the permission unset.Workflow identity mismatch. If you configured
setExpectedAuthor,setExpectedWorkflowId, orsetExpectedWorkflowNamein Step 5, verify the workflow you're deploying actually matches those values. A typo here silently blocks every execution.Wrong forwarder address. Verify the
KeystoneForwarderaddress passed to yourAutomationReceiverconstructor matches the correct address for your specific chain. This is set once at deployment and can't be changed after, so if it's wrong, you need to redeploy the receiver.
What this means beyond the migration deadline
The Bridge pattern is a migration convenience, not the end state. Once your upkeep is running on CRE, the actual value of the platform is that a workflow isn't limited to one trigger and one on-chain call the way an upkeep was. A single CRE workflow can combine multiple triggers, multiple off-chain HTTP calls, reads and writes across multiple chains, and conditional logic that would have required several separate upkeeps under the old model.
That's a conversation for a future article once you're back up and running. Right now, if your upkeep stopped executing, the priority is getting through the seven steps above to restore service.
I'm a smart contract security researcher writing through Chainlink's full architecture for 28 days. Follow along at ramprasadgoud.dev or on X @0xramprasad.
Top comments (0)