<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw" -->

---
title: Making Type Coverage Visible in Dify's CI | daily.dev
description: A case study describes how Dify, a large open-source LLM application platform with a Python Flask backend, integrated Pyrefly static type checking into its CI...
canonical: https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: Making Type Coverage Visible in Dify's CI | daily.dev
og:description: A case study describes how Dify, a large open-source LLM application platform with a Python Flask backend, integrated Pyrefly static type checking into its CI...
og:url: https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw
og:image: https://api.daily.dev/og/posts/eKiRPI3Jw.png
og:image:alt: Making Type Coverage Visible in Dify's CI
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Making Type Coverage Visible in Dify's CI

**[Pyrefly: A Fast Python Type Checker and Language Server \| Pyrefly](https://daily.dev/sources/pyrefly)** · 5 min read · 0 upvotes · 0 comments

## Summary

A case study describes how Dify, a large open-source LLM application platform with a Python Flask backend, integrated Pyrefly static type checking into its CI pipeline. Instead of blocking all diagnostics immediately, the team split rollout into a blocking check for a ready subset of files (using a local exclude list) and a full-project, non-blocking coverage report. PR comments show coverage deltas (typed/untyped symbols, coverage percentage) comparing PR branch to base branch, and a separate diagnostic diff workflow normalizes and posts only meaningful changes. Fork PRs are handled safely via artifact upload and a trusted workflow_run job to avoid granting write permissions to untrusted code. The approach treats type coverage as complementary to runtime test coverage rather than a replacement.

## Full article

daily.dev links to this article rather than hosting it. Read it at the original source: <https://pyrefly.org/blog/dify-pyrefly-coverage-ci>

## Questions this post answers

### How can I add Pyrefly type checking to a large existing Python codebase without breaking CI on day one?

Split the rollout into two CI surfaces: a blocking check that only enforces Pyrefly errors on files ready for it (using a local exclude list like pyrefly-local-excludes.txt with --project-excludes), and a separate full-project, non-blocking coverage report that posts PR comments. This lets teams see type coverage trends without forcing an immediate migration project.

_daily.dev surfaces adoption patterns like this for teams rolling out static typing gradually._

### How do you safely post CI bot comments on pull requests from forked repositories without exposing write permissions?

Upload structured artifacts from the untrusted fork-PR workflow, then use a separate workflow_run job running on trusted default-branch code to download those artifacts, render the comment, and post it. This avoids giving untrusted PR code direct write permissions while still providing contributors useful feedback.

_engineers hardening fork-PR workflows can find similar CI security patterns on daily.dev._

### What is the difference between type coverage and test coverage in a Python project?

Runtime test coverage measures whether tests executed a given piece of code, while type coverage measures whether static analysis can understand a function or module's interface. In Dify's backend, type coverage catches API drift, missing annotations, accidental Any types, and unclear boundaries, while test coverage catches behavior regressions; both are tracked separately in CI.

_daily.dev helps developers weighing type coverage against test coverage keep up with both practices._

## Similar posts on daily.dev

- [What's type-coverage, and why should I care?](https://daily.dev/posts/what-s-type-coverage-and-why-should-i-care--br4v8jndh) · Pyrefly: A Fast Python Type Checker and Language Server \| Pyrefly · 0 upvotes · 0 comments
- [Pyrefly 1.0: A fast, forward-looking Python linter](https://daily.dev/posts/pyrefly-1-0-a-fast-forward-looking-python-linter-k4vukcqpr) · InfoWorld · 0 upvotes · 0 comments
- [Pyrefly Now Type Checks PyTorch – PyTorch](https://daily.dev/posts/pyrefly-now-type-checks-pytorch-pytorch-adohlgdfc) · PyTorch · 0 upvotes · 0 comments

---

Tags: [#python](https://daily.dev/tags/python), [#cicd](https://daily.dev/tags/cicd), [#github-actions](https://daily.dev/tags/github-actions)

[View this post on daily.dev](https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"TechArticle","headline":"Making Type Coverage Visible in Dify's CI","url":"https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw","mainEntityOfPage":{"@type":"WebPage","@id":"https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw"},"datePublished":"2026-08-23T12:17:17.253Z","dateModified":"2026-08-23T12:19:51.441Z","description":"A case study describes how Dify, a large open-source LLM application platform with a Python Flask backend, integrated Pyrefly static type checking into its CI...","image":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ce7cceb523e1bdb0e6d2d6310d2d101d?_a=AQAEuop","thumbnailUrl":"https://media.daily.dev/image/upload/f_auto,q_auto/v1/posts/ce7cceb523e1bdb0e6d2d6310d2d101d?_a=AQAEuop","isAccessibleForFree":true,"articleSection":"Pyrefly: A Fast Python Type Checker and Language Server | Pyrefly","inLanguage":"en","publisher":{"@type":"Organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180}},"author":{"@type":"Organization","name":"Pyrefly: A Fast Python Type Checker and Language Server | Pyrefly","logo":"https://media.daily.dev/image/upload/s--Qzw5NpnF--/c_limit,w_256/f_auto,q_auto/v1787487420/logos/pyrefly?_a=BAMAMicg0","url":"https://daily.dev/sources/pyrefly"},"commentCount":0,"discussionUrl":"https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"keywords":"python,cicd,github-actions","timeRequired":"PT5M"}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Pyrefly: A Fast Python Type Checker and Language Server | Pyrefly","item":"https://daily.dev/sources/pyrefly"},{"@type":"ListItem","position":3,"name":"Making Type Coverage Visible in Dify's CI"}]}
{"@context":"https://schema.org","@type":"FAQPage","@id":"https://daily.dev/posts/making-type-coverage-visible-in-dify-s-ci-ekirpi3jw#faq","mainEntity":[{"@type":"Question","name":"How can I add Pyrefly type checking to a large existing Python codebase without breaking CI on day one?","acceptedAnswer":{"@type":"Answer","text":"Split the rollout into two CI surfaces: a blocking check that only enforces Pyrefly errors on files ready for it (using a local exclude list like pyrefly-local-excludes.txt with --project-excludes), and a separate full-project, non-blocking coverage report that posts PR comments. This lets teams see type coverage trends without forcing an immediate migration project. daily.dev surfaces adoption patterns like this for teams rolling out static typing gradually."}},{"@type":"Question","name":"How do you safely post CI bot comments on pull requests from forked repositories without exposing write permissions?","acceptedAnswer":{"@type":"Answer","text":"Upload structured artifacts from the untrusted fork-PR workflow, then use a separate workflow_run job running on trusted default-branch code to download those artifacts, render the comment, and post it. This avoids giving untrusted PR code direct write permissions while still providing contributors useful feedback. engineers hardening fork-PR workflows can find similar CI security patterns on daily.dev."}},{"@type":"Question","name":"What is the difference between type coverage and test coverage in a Python project?","acceptedAnswer":{"@type":"Answer","text":"Runtime test coverage measures whether tests executed a given piece of code, while type coverage measures whether static analysis can understand a function or module's interface. In Dify's backend, type coverage catches API drift, missing annotations, accidental Any types, and unclear boundaries, while test coverage catches behavior regressions; both are tracked separately in CI. daily.dev helps developers weighing type coverage against test coverage keep up with both practices."}}]}
```

