FAANGineering - BlogFlock 2026-07-14T10:20:00.747Z BlogFlock Engineering at Meta, Nextdoor Engineering - Medium, The GitHub Blog, Netflix TechBlog - Medium, Google Developers Blog, Etsy Engineering | Code as Craft Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned - Netflix TechBlog - Medium https://medium.com/p/f4b792f3f0d8 2026-07-13T22:44:11.000Z <p><em>By </em><a href="https://www.linkedin.com/in/parth-jain-8a09abb6/"><em>Parth Jain</em></a>, <a href="https://www.linkedin.com/in/raskuma/"><em>Rakesh Sukumar</em></a><em>, </em><a href="https://www.linkedin.com/in/yingwu-zhao-62037418/"><em>Yingwu Zhao</em></a><em>, </em><a href="https://www.linkedin.com/in/renzosanchezsilva/"><em>Renzo Sanchez-Silva</em></a><em> &amp; </em><a href="https://www.linkedin.com/in/nathfisher/"><em>Nathan Fisher</em></a><em><br>A deep dive into the engineering challenges of building a real-time service dependency map at Netflix scale — from streaming architectures and distributed aggregation pipelines to time-travel queries and the methodology that made it work.</em></p><h3>Introduction</h3><p>In our <a href="https://netflixtechblog.com/from-silos-to-service-topology-why-netflix-built-a-real-time-service-map-0165ba13a7bc">first post</a>, we introduced the problem: engineers at Netflix needed a unified, real-time view of service dependencies to troubleshoot faster, understand blast radius, and navigate our distributed architecture. We described our multi-source approach — combining eBPF network flows, IPC metrics, and distributed tracing into physically separate graph layers that can be queried independently or merged into a comprehensive view.</p><p>That post explained <em>what</em> we built and <em>why</em>. This post is about <em>how</em> — the engineering reality of building this system at Netflix scale.</p><p>Here’s the truth: the first version worked perfectly… in our local environment. Production was a different story. Kafka consumers fell behind. Instances ran out of memory. Some nodes received 100x the traffic of others. Garbage collection pauses consumed more CPU than actual business logic.</p><p>What you’ll learn in this post isn’t a success story — it’s a learning journey. We’ll walk through the architecture decisions that enabled scale, the production challenges that tested those decisions, the optimization methodology that guided us through, and the lessons that apply to any distributed system. Along the way, we’ll share the innovations that made it possible to process millions of flow records per second, reconstruct topology at any point in time, and provide sub-second query responses — all while maintaining near real-time freshness.</p><h3>Architecture Deep-Dive: Building for Streaming and Scale</h3><h4>Streaming-First: Why Real-Time Matters</h4><p>Traditional service topology systems use batch processing — aggregating data hourly or daily, then storing complete snapshots. This approach works at a modest scale but has a fundamental problem: by the time you see the data, it’s already old. During a production incident at 3am, an hour-old dependency map is archaeology, not observability.</p><p>Our key architectural decision was to build streaming-first. Instead of batch jobs that process historical data, we continuously ingest flow records from multi-region Kafka streams and IPC metrics as Server-Sent Events, process them through reactive pipelines with backpressure handling, and provide near real-time topology updates — typically within tens of minutes, compared to the hours-old or day-old data that batch processing approaches provide.</p><p>This wasn’t just about freshness — it was essential for our use cases. Live events can’t wait for the next hourly batch. Incident response needs current data. Change validation requires seeing immediate impact. The architecture had to support continuous updates while handling massive scale without falling behind.</p><p><strong>How Backpressure Enables Real-Time Processing<br></strong>The streaming approach created new challenges, but also required solving a fundamental problem: how do you process millions of flow records per second in real-time without losing data when downstream systems slow down?</p><p>Traditional approaches fall short at our scale:</p><ul><li><strong>Unbounded queues</strong>: Simple but dangerous. Keep buffering until you run out of memory, then the instance crashes.</li><li><strong>Drop-based flow control</strong>: Discard data when buffers fill. Fast, but now your topology is incomplete — you’ve lost connection information.</li><li><strong>Batch processing</strong>: Process everything, but hours later. By then, the incident is over (or worse, still happening with stale data).</li></ul><p>We needed something different: the ability to slow down gracefully under load without losing data. This is where reactive streams with backpressure became essential.</p><p>Here’s how it works: when Stage 3 can’t write to the graph database fast enough, it signals Stage 2 to slow down. Stage 2 signals Stage 1. Stage 1 signals the Kafka consumer to pause. The data waits in Kafka until downstream capacity returns.</p><figure><img alt="Diagram showing backpressure propagating backward through a pipeline — from Stage 3 to Stage 2 to Stage 1 to the message stream — each stage signaling the previous one to slow dow" src="https://cdn-images-1.medium.com/max/947/1*dgCgcPQv-EvBNb_AXqtnhA.png" /><figcaption>When a downstream stage can’t keep up, it signals upstream to slow down — backpressure flows in the opposite direction of the data</figcaption></figure><p>Backpressure propagates naturally through the entire system. When any stage becomes overwhelmed — from traffic spikes, GC pauses, or external slowdowns — the pipeline automatically slows to a sustainable rate. No data is lost in most cases, no instances crash, the system degrades gracefully.</p><p>This is what enables “real-time” at our scale. During normal operation, we process with minimal latency. During load spikes or temporary slowdowns, we slow down rather than fall over. The data still gets processed — just a few seconds or minutes later instead of immediately. For topology updates, this trade-off is acceptable: slightly delayed real-time updates are vastly better than hour-old batch data or incomplete topology from dropped records.</p><p>The cost of this approach is complexity. Reactive streams are harder to reason about compared to traditional synchronous blocking models (we’ll discuss this more in the challenges section). But at Netflix scale, backpressure isn’t optional — it’s the mechanism that keeps the system running reliably under production load.</p><h4>Multi-Layer Architecture: Physical Separation for Independent Optimization</h4><p>As we covered in our <a href="https://netflixtechblog.com/from-silos-to-service-topology-why-netflix-built-a-real-time-service-map-0165ba13a7bc">first post</a>, our multi-source approach uses three physically separate topology layers with different storage optimized for each:</p><ul><li><strong>Network Layer</strong>: eBPF flow logs in graph database partition — comprehensive coverage but lacks application context</li><li><strong>IPC Layer</strong>: Application metrics in a different graph database isolated from the one for Network Layer — rich endpoint details but only instrumented services</li><li><strong>Tracing Layer</strong>: Distributed traces in columnar storage (Parquet) — actual request paths but sampled.(<em>We cover the tracing layer and its integration in our next post</em><strong><em>)</em></strong>.</li></ul><figure><img alt="Diagram showing two separate ingestion pipelines — a flow log pipeline and an IPC pipeline, each fed by data enrichment — writing to their own graph store, with a shared API serving UI and backend clients" src="https://cdn-images-1.medium.com/max/1024/1*5_09VEnyPtFR0tbO1zzzMQ.png" /><figcaption>Flow logs and IPC metrics travel through two independently-optimized pipelines into separate graph stores, unified behind a single API</figcaption></figure><p>Physical storage isolation enables independent optimization — each layer has different throughput, query patterns, and evolution timelines. At query time, we execute parallel queries across relevant storage systems and merge results, providing unified views with sub-second latency while maintaining flexibility to evolve each layer independently.</p><h4>The Three-Stage Distributed Aggregation Pipeline</h4><p>The heart of the network layer ingestion is a three-stage distributed pipeline. This architecture solves a fundamental challenge with network flow logs: <strong>they only show individual network hops, not the true application-level connections we need to build a useful topology</strong>.</p><p><strong>The Core Problem: Network Intermediaries</strong></p><p>In cloud environments, traffic between applications rarely flows directly — it traverses intermediate network components like load balancers, NAT gateways, API gateways, and proxies. Network flow logs show individual hops: App A → Load Balancer and Load Balancer → App B appear as separate flows. But what engineers need is the logical dependency: App A → App B. Without resolving these intermediaries, our topology would be cluttered with infrastructure components rather than showing the service-to-service relationships that matter for troubleshooting.</p><p>The three-stage pipeline solves this:</p><figure><img alt="Diagram of the flow log pipeline showing a message stream flowing through Stage 1, Stage 2, and Stage 3 via SSE, with data enrichment feeding into Stage 3 before writing to the network graph store" src="https://cdn-images-1.medium.com/max/1024/1*LWO54sgNSsNjNda_vG3OhQ.png" /><figcaption>The flow log pipeline in detail — three stages connected by SSE, with enrichment applied just before the final graph write</figcaption></figure><p><strong>Stage 1: Initial Aggregation (FlowLog Ingestion Service)</strong></p><pre>Multi-Region Kafka (4 regions)<br> → Filter invalid flow logs<br> → 5-minute time-window batching<br> → Create initial aggregators per window<br> → Distribute via consistent hashing<br> → Stream to Stage 2 via SSE</pre><p>Stage 1 consumes flow logs from multi-region Kafka, filters invalid records, batches them into 5-minute time windows, and creates initial aggregator objects. At this stage, we’re still working with raw network hops — identifying which flows involve intermediaries but not yet resolving them. Aggregators stream to Stage 2 for resolution.</p><p><strong>Stage 2: Network Intermediary Resolution Layer (Intermediate GraphEntity Ingestion Service)</strong></p><pre>Stage 1 Aggregators (via SSE streams)<br> → Group flows by intermediary (load balancer, NAT gateway, proxy, etc.)<br> → Identify pairs: (Source → Intermediary) + (Intermediary → Destination)<br> → Resolve to direct edges: Source → Destination<br> → Track which intermediaries were traversed<br> → Aggregate metrics across both hops<br> → Re-distribute via consistent hashing<br> → Stream to Stage 3 via SSE</pre><p>This is the key step<strong>.</strong> Stage 2 performs graph resolution:</p><ol><li><strong>Collect flows by intermediary</strong>: Group aggregators where an intermediary is either source or destination — creating maps of flows going TO intermediaries (Source → Intermediary) and FROM intermediaries (Intermediary → Destination)</li><li><strong>Resolve direct edges</strong>: For each intermediary, join its incoming and outgoing flows to create direct application edges (App A → App B), combining metrics from both hops</li><li><strong>Result</strong>: Clean application-level topology showing App A → App B instead of App A → Load Balancer → App B</li></ol><p>This resolution happens at aggregation time, not query time, with resolved edges flowing to Stage 3.</p><p><strong>Why can’t we do this in a single stage?</strong> The fundamental issue is <strong>data locality</strong>. To resolve App A → Load Balancer → App B into App A → App B, we need both flows on the same instance to perform the join. But in Stage 1, flows are scattered across instances based on Kafka’s partitioning. Stage 2’s critical function is to redistribute aggregators by intermediary identifier — all flows involving “Load Balancer X” route to the same instance for resolution. This is the classic map-reduce pattern: Stage 1 maps, Stage 2 shuffles and reduces by intermediary, Stage 3 performs final aggregation.</p><figure><img alt="Three-panel diagram showing how flow records for services A, B, C, D and load balancers LB1 and LB2 are scattered across instances in Stage 1, reshuffled and resolved into direct edges in Stage 2, and combined and persisted to the graph store in Stage 3." src="https://cdn-images-1.medium.com/max/1024/1*thv_UxYLRCf_IbLJPe_3Sw.png" /><figcaption>A concrete example of why a single stage isn’t enough — Stage 1 scatters flows by partition, Stage 2 reshuffles by intermediary to resolve direct edges, and Stage 3 persists the final result.</figcaption></figure><p><strong>Stage 3: Final Aggregation and Enrichment (GraphEntity Ingestion Service)</strong></p><pre>Stage 2 Aggregators (via SSE streams)Flow<br> → Final aggregation across time windows<br> → Enrich with external data (query key-value stores)<br> → Convert to graph entities<br> → Persist to graph database (throttled writes)</pre><p>Stage 3 performs final aggregation of resolved edges, enriches graph nodes with external data sources (application health, ownership, metadata), converts aggregators to concrete graph entities (nodes and edges with all properties populated), and persists them to the distributed graph database with controlled throttling to respect storage system limits.</p><p><strong>Why Three Stages, Not Two?</strong></p><p>We initially used two stages: aggregate in Stage 1, resolve and persist in Stage 2. This worked in testing but failed at production scale — Stage 2 became overwhelmed by data concentration.</p><p>The problem: intermediary resolution requires collecting ALL flows involving an intermediary on the same instance.As a result, the instances handling flow logs for popular applications and their intermediaries became ‘hot nodes’ due to significant data concentrationCompounding this, data enrichment (querying external stores for health and metadata) meant the busiest instances were also doing the most I/O.</p><p>The solution: split responsibilities into three stages. Stage 2 focuses purely on resolution and redistributes. Stage 3 handles enrichment and persistence. This graduated redistribution — distribute, resolve, distribute again, persist — spreads load across multiple instances and isolates compute-heavy resolution from I/O-heavy enrichment. Even when intermediaries see 100x typical traffic, no single instance becomes a bottleneck.</p><p><strong>Why Server-Sent Events Instead of gRPC or Message Queues?</strong></p><p>We initially used gRPC but it became a performance bottleneck — serialization overhead, connection pool management, and memory pressure for streaming responses consumed more CPU than business logic. Message queues added infrastructure complexity without benefit for our use case.</p><p>SSE proved ideal: lightweight HTTP-based protocol with minimal serialization, natural backpressure integration with reactive streams, and simpler connection model. The lesson: industry best practices like “use gRPC for service communication” don’t apply universally. For streaming large volumes of pre-aggregated data, lighter-weight alternatives may be more appropriate. Measure, don’t assume.</p><p><strong>Why IPC Doesn’t Need Three Stages</strong></p><figure><img alt="Diagram of the IPC pipeline showing an IPC metrics stream flowing via SSE into a single aggregation stage, with data enrichment feeding into that stage, before writing to the IPC graph store." src="https://cdn-images-1.medium.com/max/763/1*vHVMSq80gccWg78809kkDg.png" /><figcaption>The IPC pipeline mirrors the same pattern as the flow log pipeline, but needs only a single stage.</figcaption></figure><p>The IPC layer uses single-stage aggregation because: (1) IPC metrics are already at application level — no intermediaries to resolve, and (2) data is partitioned correctly from the start — each node receives all IPC metrics for its assigned applications via consistent hashing, eliminating the need for redistribution. This highlights a key principle: <strong>data partitioning strategy determines processing architecture</strong>. When data arrives with the right partitioning, you can aggregate directly; when it doesn’t (like network flows requiring intermediary resolution), you need shuffle/redistribution stages.</p><h4>Dynamic Load Distribution: How Hashing Works with Auto-Scaling</h4><p>How do we decide which instance receives which aggregator when our Auto Scaling Groups dynamically add or remove instances? Traditional approaches assume static clusters — requiring explicit rebalancing, coordination services, or manual data movement when cluster size changes.</p><p><strong>Our Approach: Dynamic Consistent Hashing</strong></p><p>We use consistent hashing with dynamic instance discovery from our service registry. Each instance queries the registry to get the current list of healthy ASG instances, maintains them in sorted order (ensuring all instances have the same view), and uses this list for the hash function findOwnerInstance(aggregator.primaryKey). When ASG scales up or down, the hash function naturally redistributes aggregators based on the updated instance list — no explicit coordination needed.</p><p>The key insight: leverage existing infrastructure. Our service registry already tracks ASG membership for health checking. Using it as our source of truth gives us dynamic cluster membership for free. Consistent hashing provides stable partitioning (most aggregators stay on the same instance during membership changes), while the sorted list ensures consistency.</p><p><strong>The Result</strong></p><p>Load follows infrastructure automatically. During traffic spikes or live events, new instances immediately receive their share. During deployments, aggregators seamlessly shift to healthy instances. This pattern proved crucial for production stability — no manual intervention, no coordination protocol, just automatic rebalancing.</p><h3>The V1 Journey: Major Challenges at Production Scale</h3><p>Getting the initial version (V1) to production taught us that scale changes everything. What works in development breaks in production. Every assumption gets tested. And fixing one bottleneck reveals the next.</p><h4>Challenge 1: Kafka Consumer Lag</h4><p><strong>The Problem</strong>: Our multi-region Kafka consumers started falling behind — consumer lag grew from seconds to minutes, then hours. Flow logs were arriving faster than we could process them. If this continued, we’d never catch up, and our “real-time” topology would become increasingly stale.</p><p><strong>Investigation</strong>: We instrumented Kafka consumer metrics heavily. Key findings:</p><ul><li>Kafka had fewer partitions than optimal for our consumer group size</li><li>Each fetch operation retrieved relatively few records</li><li>Network socket buffers weren’t right-sized for our throughput</li><li>Cross-region read latency added overhead</li></ul><p><strong>Solutions Applied</strong>:</p><ol><li><strong>Increased Kafka partitions</strong>: More partitions enabled more parallel consumers in our consumer group, distributing load across more instances.</li><li><strong>Tuned fetch parameters</strong>: Increased records per fetch operation, reducing the number of network round-trips. This trades off per-message latency (we fetch larger batches) for throughput (more records processed per second).</li><li><strong>Increased socket receive buffer size</strong>: Ensured network buffers never limited fetch operations. At our scale, default buffer sizes were too small.</li></ol><p><strong>Results</strong>: Throughput improved significantly, and lag reduced to acceptable levels — typically under a minute even during peak traffic.</p><p><strong>Lesson</strong>: At scale, you can’t optimize in isolation. Fixing Kafka lag revealed the next bottleneck: our instances themselves couldn’t keep up with the higher ingest rate. The pipeline moved faster, which exposed downstream capacity problems.</p><h4>Challenge 2: Hot Nodes and Data Amplification</h4><p><strong>The Problem</strong>: This was the most severe production issue we faced. Some instances in our Auto Scaling Group were receiving 100x more traffic than others. Memory usage spiked. Garbage collection pauses became frequent and long. More CPU time was spent in GC than in business logic. Eventually, hot instances would go DOWN, triggering cascading failures as their load redistributed to other instances.</p><p><strong>Root Cause Investigation</strong>:<br>Flow logs for popular services dominate traffic volume. A service like our authentication layer or recommendation API is called by hundreds of other services, generating orders of magnitude more flow records than typical services.</p><p>Our initial architecture used consistent hashing to determine which instance owned aggregation for each destination service. All flow logs for a given destination are routed to the same instance — the “owner” for that destination. This design seemed reasonable: group related data for efficient aggregation.</p><p>But popular destinations created hot nodes. One instance might own authentication services, another might own a rarely-used backend service. The load distribution was wildly uneven — some instances handled 100x the flow records of others.</p><p>Worse, data amplification occurred during redistribution. Consider a service called by 100 upstream services across 10 instances. All 10 instances receive flow logs for that destination (because they all have local clients calling it). When they route aggregators to the owner instance, that instance receives 10 separate aggregators it must merge. The data volume multiplied during shuffling.</p><figure><img alt="Diagram showing many instances each sending aggregators for the same destination into a single owner instance, illustrating how data volume multiplies at the point of convergence" src="https://cdn-images-1.medium.com/max/594/1*MmWlcA1C2o-z_Zui1wCarA.png" /><figcaption>When many instances route data for the same key to one owner, the volume multiplies right where it lands — the root cause of hot nodes.</figcaption></figure><p>We profiled extensively using async-profiler and heap dump analysis. The results were clear: hot instances spent most of their CPU on garbage collection, trying to manage the rapid allocation and deallocation of aggregator objects as flow logs poured in faster than they could be processed. Memory pressure led to GC thrashing, which consumed CPU, which slowed processing, which increased memory pressure — a vicious cycle.</p><p><strong>Solution: The Three-Stage Pipeline’s Dual Benefits<br></strong>The three-stage pipeline we described earlier — designed primarily for proxy resolution — turned out to be exactly what we needed to solve the hot nodes problem as well. Here’s why:</p><p><strong>Stage 1</strong> performs initial aggregation locally before any distribution. Instead of sending every flow log to a remote instance immediately. Each instance performs online aggregation of raw flow logs into time-windowed aggregators (over 5-minute periods) directly in memory; this allows the raw flow to be discarded and garbage collected quickly,, significantly reducing memory pressure, and ensures only the aggregation results are transferred across the network to downstream stages.</p><p><strong>Stage 2</strong> focuses on proxy resolution but also provides intermediate redistribution. Aggregators from Stage 1 distribute via consistent hashing to Stage 2 instances. Now we’re moving compressed aggregators, not individual flow logs. After resolution, Stage 2 redistributes resolved edges again to Stage 3, providing a second hashing operation that further spreads load.</p><p><strong>Stage 3</strong> receives resolved aggregators that have been compressed twice and distributed twice. Even for extremely popular services, load has been spread across enough distribution points that no single instance becomes overwhelmed.</p><p>The key insight: architectural decisions driven by one requirement (proxy resolution) often solve other problems (load distribution) as beneficial side effects. The three-stage pipeline with graduated redistribution achieves both goals — it resolves proxies to show clean application-level topology AND prevents hot nodes by spreading load across multiple distribution points.</p><p><strong>Switching from gRPC to SSE<br></strong>As described earlier, this challenge also revealed that gRPC wasn’t the right protocol for inter-stage communication at our scale. We replaced gRPC with Server-Sent Events, dramatically reducing resource consumption on both sender and receiver sides.</p><p><strong>Results</strong>:</p><ul><li>CPU usage became evenly distributed across instances — no more hot nodes with 10x the load of others</li><li>Network bandwidth usage dropped significantly due to better aggregation and lighter-weight protocol</li><li>Memory pressure decreased as we reduced the object allocation rate</li><li>The system scaled gracefully with Auto Scaling Group changes</li></ul><p><strong>Lesson</strong>: Technology choices must match your specific use case. gRPC is excellent for request-response RPC patterns. For streaming large volumes of aggregated data in a pipeline, lighter-weight alternatives can be more appropriate. Let measurements guide the decision, not industry hype or existing team expertise.</p><h4>Challenge 3: Memory and Garbage Collection</h4><p><strong>The Problem</strong>: Even after fixing hot nodes, we still saw high heap usage, frequent garbage collection pauses, and instances occasionally going DOWN. GC logs showed pauses consuming significant CPU time — in some cases, more than our business logic.</p><p><strong>Root Cause</strong>: Multiple factors contributed: objects accumulating in heap while waiting for 5-minute aggregation windows to complete, unnecessary conversions between different object types as data flowed through stages, and immutability overhead — following Scala best practices, we used immutable data structures for aggregators, but every update created new objects, overwhelming the garbage collector at millions of records per second.</p><p><strong>Investigation</strong>: Heap dumps and GC logs revealed flow log objects retained beyond their useful lifetime, unnecessary intermediate conversion objects, and constant creation/disposal of immutable aggregator versions. Minor GCs occurred every few seconds, major GCs took hundreds of milliseconds — the JVM spent more time on garbage collection than business logic.</p><p><strong>Solutions Applied</strong>:</p><ol><li><strong>Faster processing</strong>: Process flow logs immediately, aggregate quickly, release references. Optimized Pekko stream stages to minimize object lifetime.</li><li><strong>Eliminate unnecessary conversions</strong>: Route aggregators directly between stages instead of converting to intermediate types.</li><li><strong>Mutable structures on hotpath</strong>: This was controversial — Scala best practices emphasize immutability. But at our scale, immutability created too many objects. We pragmatically chose mutable aggregators on the hotpath (immutability elsewhere), prioritizing performance over convention. Switching to mutable aggregators reduced heap allocation by over 50% and cut GC pause time significantly, though it required more careful code review.</li><li><strong>Tuned time windows</strong>: Balanced data freshness against memory pressure.</li></ol><p><strong>Results</strong>:</p><ul><li>Heap usage decreased substantially</li><li>GC pauses reduced to acceptable levels (tens of milliseconds instead of hundreds)</li><li>CPU freed up for business logic instead of garbage collection</li><li>Instance stability improved — no more instances going DOWN due to memory issues</li></ul><p><strong>Lesson</strong>: “Best practices” are starting points, not absolute rules. At unique scale, you may need to diverge from conventions. But do it deliberately, with measurement justifying the decision, and with awareness of the trade-offs. Don’t abandon immutability everywhere — just where performance data proves it’s necessary.</p><h3>Challenge 4: Reactive Streams Complexity</h3><p><strong>The Problem</strong>: Our Pekko Streams pipelines would stall unexpectedly. Backpressure propagation didn’t work as expected. We struggled to debug why certain streams would stop processing without obvious errors. The reactive programming mental model — with its emphasis on async boundaries, backpressure, and demand-driven processing — proved harder to master than anticipated.</p><p><strong>What We Learned</strong>:<br>Reactive streams with backpressure are powerful tools for building systems that handle load spikes gracefully. When downstream consumers slow down (due to temporary load, GC pauses, or external system slowdowns), backpressure allows upstream producers to slow down rather than overflow buffers or drop data.</p><p>But this power comes with complexity:</p><ul><li><strong>Non-intuitive behavior</strong>: Traditional imperative code flows top-to-bottom. Reactive streams are demand-driven — downstream consumers pull from upstream producers. This inversion of control isn’t intuitive.</li><li><strong>Async boundaries</strong>: The .async operator in Pekko Streams creates a boundary where processing moves to a different thread. This can improve parallelism but also introduces complexity around buffer sizing, demand signaling, and error propagation. We initially misunderstood when to use .async and ended up with over-parallelized streams that created more overhead than benefit.</li><li><strong>Debugging difficulty</strong>: When a stream stalls, there’s no stack trace pointing to the problem. You must understand the internal mechanics — demand signals, buffer states, materializer state — to diagnose issues.</li></ul><p><strong>Our Approach</strong>:</p><ol><li><strong>Deep learning investment</strong>: We invested significant time in understanding reactive streams concepts deeply. Reading documentation, experimenting with small examples, and building team expertise.</li><li><strong>Simplified patterns</strong>: Where possible, we simplified our stream graphs. Complex branching and merging patterns are powerful but hard to debug. We preferred linear flows with clear stage boundaries.</li><li><strong>Better monitoring</strong>: We added metrics at stream boundaries — tracking buffer sizes, element throughput, backpressure events. Visibility into stream internals helped diagnose issues.</li><li><strong>Team education</strong>: We documented our learnings, shared patterns that worked, and built institutional knowledge about reactive streams.</li></ol><p><strong>Lesson</strong>: Powerful abstractions require investment. Don’t assume you understand a framework without validation. Build your mental model deliberately, test it with experiments, and be humble about your understanding. Reactive streams are worth mastering for systems that need to handle load gracefully, but expect a learning curve.</p><h3>V2 Evolution: Continuous Refinement</h3><p>V1 got us to production. The major architectural challenges — Kafka lag, hot nodes, memory pressure — were solved. But production at full scale revealed new optimization opportunities. V2 represents the continuous refinement that turns a working system into a production-ready system.</p><h4>Challenge 5: Persistent Heap Pressure</h4><p><strong>The Problem</strong>: Despite V1 optimizations, we still observed higher-than-desired heap usage. GC metrics improved but weren’t optimal. Memory profiling showed room for improvement.</p><p><strong>Root Cause</strong>: Deeper analysis revealed we were still doing unnecessary object conversions between stages. We’d convert aggregators to full graph entities (with all properties populated) before routing to the next stage, even though the next stage just needed the compressed aggregator state.</p><p><strong>Solution</strong>: Architectural change to route aggregators directly through all stages, only converting to final graph entities at Stage 3 immediately before persistence. This eliminated two intermediate conversion steps and the associated object allocation.</p><p><strong>Result</strong>: Heap usage dropped further, GC pauses became even less frequent, and memory headroom improved.</p><h4>Challenge 6: Serialization Complexity</h4><p><strong>The Problem</strong>: Custom serialization logic for SSE messages caused occasional erratic errors that were hard to reproduce and debug. Different parts of the codebase used inconsistent serialization approaches.</p><p><strong>Solution</strong>: Standardized on JSON encoding throughout the pipeline. While slightly less efficient than binary serialization, JSON’s human readability made debugging far easier, and the overhead was negligible compared to other operations. Consistency eliminated an entire class of bugs.</p><p><strong>Result</strong>: Serialization-related errors disappeared. Debugging became easier because we could read SSE message contents directly.</p><h4>Challenge 7: Stream Processing Inefficiencies</h4><p><strong>The Problem</strong>: Even after understanding reactive streams better, our Pekko configurations weren’t optimal. We had over-parallelized some stages and under-parallelized others. The .async boundaries weren’t placed optimally.</p><p><strong>Solution</strong>: Through continued profiling and experimentation, we tuned parallelism parameters, adjusted buffer sizes, and refined async boundary placement. We added monitoring at stream boundaries to identify bottlenecks.</p><p><strong>Result</strong>: Throughput improvements and more consistent processing latency.</p><h4>Challenge 8: Uneven Graph Database Throughput</h4><p><strong>The Problem</strong>: Write distribution to our graph database wasn’t even. Some partitions received heavy write traffic while others sat idle. This caused throttling to kick in unevenly and limited overall write throughput.</p><p><strong>Solution</strong>: Implemented batching of aggregators before writing to the graph database and improved distribution logic across partitions. Rather than writing each aggregator immediately, we batch them and write multiple entities in coordinated operations.</p><p><strong>Result</strong>: More consistent write throughput and better utilization of database capacity.</p><h4>Challenge 9: Data Enrichment at Aggregation Time</h4><p>Beyond the core topology graph, we needed to enrich nodes with additional context. At Stage 3, before persisting graph entities, we integrate enrichment data from external sources — application health status, ownership information, and other metadata. Performing this enrichment at aggregation time rather than at query time avoids the performance overhead of post-query joins and ensures every topology node has full context when queried.</p><h4>Pattern Recognition</h4><p>Each V2 challenge followed the same pattern: production revealed an assumption, profiling identified the root cause, targeted fixes improved specific metrics. Measure, hypothesize, validate, iterate. This is how you build at scale — not by getting everything right upfront, but by continuous learning and improvement.</p><h3>Time Travel: Continuous Topology Reconstruction</h3><p>One of the most powerful capabilities we built enables querying historical topology: “What did the call graph look like when this incident happened?” This time-travel feature required solving an interesting architectural challenge — how to efficiently store and reconstruct topology across time.</p><h4>The Problem</h4><p>Engineers need to answer temporal questions: What did the topology look like during an incident? How have dependencies evolved? Traditional approaches — full snapshots or event sourcing — either have exponential storage costs or require slow log replay.</p><h4>Our Approach: Time-Windowed Aggregators with Mutation Tracking</h4><p>We combine two mechanisms:</p><p><strong>1. Time-Windowed Aggregator Snapshots</strong>: Every aggregator stores startTs and endTs timestamps for its 5-minute window. These immutable aggregators persist in the graph database keyed by (entity_id, timestamp), providing checkpoint states every 5 minutes.</p><p><strong>2. Property-Level Mutation Tracking</strong>: The graph database maintains mutation history at the property level — storing only changed properties with timestamps. This is much more efficient than full entity copies and provides sub-window precision beyond the 5-minute aggregation boundaries.</p><p><strong>3. Query-Time Reconstruction</strong>: When querying historical topology, we query the mutation history API for the time range, retrieve all mutations, and reconstruct topology state by applying mutations in order.</p><p>This approach provides efficient storage (compressed aggregator states + sparse property mutations), fast retrieval (indexed mutation history, no log replay), and flexible analysis (arbitrary time ranges without pre-computing all possibilities).</p><p><strong>Query-Time Re-Aggregation</strong>: We can further aggregate historical data at query time using the same aggregator classes from ingestion. This enables arbitrary groupby dimensions (availability tier, business domain, deployment cluster) that weren’t pre-computed, allowing exploratory analysis without exploding storage costs.</p><h3>Lessons for Distributed Systems</h3><p>While these challenges were specific to service topology, the lessons apply broadly to distributed systems at scale.</p><h4>Scale Changes Everything</h4><p>What works at 100 requests per second fails at 100,000 requests per second. The change isn’t linear — it’s qualitative. Approaches that are fine at modest scale hit fundamental walls at extreme scale.</p><p>Examples from our journey: immutable data structures create GC pressure at millions of allocations per second; single-stage aggregation fails catastrophically with power-law traffic distribution; standard gRPC becomes heavyweight for streaming aggregation at volume.</p><p>The lesson: be willing to break conventional wisdom when scale justifies it. But do it based on measurement, not speculation.</p><h4>Optimize One Bottleneck at a Time</h4><p>Distributed systems have cascading bottlenecks. Fix Kafka lag, and you discover hot node issues. Fix hot nodes, and you discover GC problems. Fix GC, and you discover serialization inefficiencies.</p><p>This isn’t failure — it’s the nature of complex systems. Each optimization raises throughput, which stresses the next weakest point. The approach: prioritize based on impact, fix the current bottleneck thoroughly with measurement confirming resolution, then move to the next one. Optimization at scale is continuous, not one-time.</p><h4>Distribution Is Key to Scale</h4><p>Single aggregation points are inevitable bottlenecks. Consistent hashing distributes load but doesn’t prevent concentration when data itself is unevenly distributed (power-law distributions like ours).</p><p>Our three-stage pipeline with graduated redistribution solved this. Load spreads across multiple distribution points at each stage. Even with highly skewed data, no single instance becomes overwhelmed. The general principle: use multi-stage processing with redistribution at each stage when dealing with skewed data at scale.</p><h3>Current State and Impact</h3><p>Service Topology operates in production today, processing flow logs, ipc metrics and traces from multiple regions and serving queries with sub-second latency. Teams across Netflix use it daily for incident investigation, blast radius analysis, dependency understanding, and production change management. The system has become essential infrastructure for maintaining reliability at scale.</p><h3>Conclusion</h3><p>Service Topology at Netflix represents a journey through building distributed systems at scale. We started with engineers struggling to understand dependencies across scattered tools. We built a multi-layer architecture using streaming aggregation, network intermediary resolution, and time-travel capabilities. And we learned that optimization at scale is continuous — measure, iterate, validate, repeat.</p><p>The challenges we faced — Kafka lag, hot nodes, memory pressure — required breaking conventional wisdom when data justified it. Each fix revealed the next bottleneck. But that iterative process, guided by constant measurement, is what makes systems work at extreme scale.</p><p>In our next post, we’ll explore the tracing layer integration, unified querying across heterogeneous storage, and how all three layers combine to provide comprehensive topology visibility.</p><h3>Acknowledgements</h3><p><em>Service Topology was built by </em><a href="https://www.linkedin.com/in/parth-jain-8a09abb6/"><em>Parth Jain</em></a><em>, </em><a href="https://www.linkedin.com/in/raskuma/"><em>Rakesh Sukumar</em></a><em>, </em><a href="https://www.linkedin.com/in/yingwu-zhao-62037418/"><em>Yingwu Zhao</em></a><em>, </em><a href="https://www.linkedin.com/in/renzosanchezsilva/"><em>Renzo Sanchez-Silva</em></a><em>, and </em><a href="https://www.linkedin.com/in/nathfisher/"><em>Nathan Fisher</em></a><em>.</em></p><p><em>Special thanks to the many engineers across Netflix who made this possible — the Observability team who built the broader system, the graph database platform team who provided the storage foundation, and the Platform Modernization Engineering, and Live teams who provided invaluable feedback and use cases throughout development.</em></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f4b792f3f0d8" width="1" height="1" alt=""><hr><p><a href="https://netflixtechblog.com/building-service-topology-at-scale-architecture-challenges-and-lessons-learned-f4b792f3f0d8">Building Service Topology at Scale: Architecture, Challenges, and Lessons Learned</a> was originally published in <a href="https://netflixtechblog.com">Netflix TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p> Modernizing the Meta Ads Service With an Open-Source Kernel Scheduler - Engineering at Meta https://engineering.fb.com/?p=24221 2026-07-13T16:00:50.000Z <h1></h1> <h1><span style="font-weight: 400;">TL; DR</span></h1> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">At Meta&#8217;s scale, a few milliseconds of latency degradation can have a significant negative impact on ads performance. </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">When a Linux kernel upgrade risked regressing latency across Meta&#8217;s ad serving fleet, we turned to sched_ext — the upstream, BPF-based extensible scheduling framework — to build a scheduling policy customized to the Ads delivery workload.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The result: a 28% reduction in ads retrieval stage tail(99th percentile) latency, 3.28 megawatts(MW) power saving, and a 1.1% increase in the number of ads ranked, proving that workload-specific scheduling optimization can directly drive business value.</span></li> </ul> <h1><span style="font-weight: 400;">Why Ads Latency Matters</span></h1> <p><span style="font-weight: 400;">Meta&#8217;s ads serving fleet handles more than 5 million requests per second on average at the serving platform entry point, which is over 400 billion per day across all monetized surfaces<sup>1</sup>.</span><span style="font-weight: 400;"> Every millisecond shaved off the p99 latency makes the ads more relevant for people on our platforms, and better matches mean stronger ROI for advertisers.</span></p> <p><img fetchpriority="high" decoding="async" class="alignnone size-full wp-image-24224" src="https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp" alt="" width="1999" height="1171" srcset="https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp 1999w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=916,537 916w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=768,450 768w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=1024,600 1024w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=1536,900 1536w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=96,56 96w, https://engineering.fb.com/wp-content/uploads/2026/07/image1.webp?resize=192,112 192w" sizes="(max-width: 992px) 100vw, 62vw" /></p> <p><span style="font-weight: 400;">This provides a real opportunity to reduce latency through workload-specific scheduling. That is why our Ads and Linux Kernel teams have been working together to build a scheduling policy customized to the ads delivery workload using sched_ext, the upstream, BPF-based extensible scheduling framework. Until now, we have been using the general-purpose schedulers typically integrated in the Linux kernel (CFS and EEVDF) that balance threads across CPUs with no understanding of the workload. However, here we know the purpose and importance of each thread. With sched_ext, we can encode this knowledge directly into the scheduler. Work that improves the p99 request latency is scheduled first, and everything else takes a back seat.</span></p> <h2><span style="font-weight: 400;">sched_ext at Meta</span></h2> <p><span style="font-weight: 400;">s</span><span style="font-weight: 400;">ched_ext is an open-source, BPF-based scheduler framework that officially entered kernel v6.12. We developed it by partnering with the authors of Google’s </span><a href="https://dl.acm.org/doi/epdf/10.1145/3477132.3483542"><span style="font-weight: 400;">ghOS</span></a><span style="font-weight: 400;">t to design a scheduler suitable for upstream Linux integration. It has already been deployed in several services at Meta, delivering meaningful reductions in scheduling latency.</span></p> <p><span style="font-weight: 400;">While upgrading our fleet to the latest stable version of Linux (</span><a href="https://kernelnewbies.org/Linux_6.9#:~:text=Display,Sunday%2C%2012%20of%20May%202024"><span style="font-weight: 400;">kernel v6.9</span></a><span style="font-weight: 400;">) we observed that the new </span><a href="https://docs.kernel.org/scheduler/sched-eevdf.html"><span style="font-weight: 400;">Earliest Eligible Virtual Deadline First (EEVDF)</span></a><span style="font-weight: 400;"> scheduler introduced in </span><a href="https://kernelnewbies.org/Linux_6.6"><span style="font-weight: 400;">Linux kernel v6.6</span></a><span style="font-weight: 400;"> was causing a latency regression which reduced the number of ads ranked in response. As a result, a subset of ads hosts were forced to remain on the older v6.4 kernel, creating technical debt and operational fragmentation.</span></p> <p><span style="font-weight: 400;">Given its already strong performance, sched_ext was a great candidate to address these scheduling regressions.</span></p> <h2><span style="font-weight: 400;">Custom Scheduling with sched_ext</span></h2> <p><span style="font-weight: 400;">Sched_ext lets scheduler developers implement their preferred </span><b>scheduling policy</b><span style="font-weight: 400;"> as a BPF program. When a host starts running the ads workload, an ads-optimized policy is applied. From that point on, the kernel calls into the BPF scheduler through a set of event-driven callbacks to handle common scheduling events, such as:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><b>Thread wake-up</b><span style="font-weight: 400;">: choose a CPU when a thread becomes runnable.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Enqueue</b><span style="font-weight: 400;">: place a thread in a run queue.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Dispatch</b><span style="font-weight: 400;">: select the next thread when a CPU becomes idle.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Idle transitions</b><span style="font-weight: 400;">: respond to CPUs entering/leaving idle states.</span></li> </ul> <p><span style="font-weight: 400;">At a high level, the policy </span><b>soft-partitions CPUs into two pools</b><span style="font-weight: 400;">, one for threads on the latency-critical request path and one for less latency-sensitive work. Which thread goes into which pool is part of the domain-specific knowledge encoded inside the policy. The size of each pool is adjusted dynamically using load-based heuristics. This approach tends to keep related work on the same CPUs over time, improving </span><b>last-level cache (L3) locality</b><span style="font-weight: 400;"> and reducing costly DRAM access.</span></p> <p><span style="font-weight: 400;">The policy is packaged as a user-space binary that loads the BPF program. That design makes experimentation and performance optimization much faster. To roll out a change, we can simply restart the scheduler process to unload the old policy and load the new one, without rebuilding or reinstalling the kernel.</span></p> <p>&nbsp;</p> <h1><span style="font-weight: 400;">Results and Impact</span></h1> <p><b>The initial launch </b><span style="font-weight: 400;">took place to switch from kernel 6.4 with the CFS scheduler to kernel 6.9 with sched_ext on the largest ads serving server type. Based on the backtest experiment, the launch delivered:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><b>+1.1% on weighted-ads-ranked</b><span style="font-weight: 400;"> (metric for number of ads retrieved and ranked).</span></li> <li style="font-weight: 400;" aria-level="1"><b>3.28 megawatts</b><span style="font-weight: 400;"> of power savings across the fleet.</span></li> <li style="font-weight: 400;" aria-level="1"><b>28% reduction in service p99 latency</b><span style="font-weight: 400;"> on the ads retrieval path<sup>2</sup>.</span></li> </ul> <p><b>Compounding improvements.</b><span style="font-weight: 400;"> Two follow-on scheduler-policy updates, delivered as purely user-space changes, extended the win:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><b>Additional 60% reduction in service p99 latency.</b></li> <li style="font-weight: 400;" aria-level="1"><b>18% reduction in timeout errors on the critical path.</b></li> </ul> <p><span style="font-weight: 400;">This is a non-trivial win delivered with no dependency on kernel releases. Each follow-on iteration above shipped in days rather than months because the scheduler policy lives in user space as a BPF program. That cadence is what turned sched_ext from a &#8220;kernel upgrade unblocker&#8221; to a continuous-optimization platform for ads serving.</span></p> <p><img decoding="async" class="alignnone size-full wp-image-24228" src="https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg" alt="" width="1376" height="768" srcset="https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg 1376w, https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg?resize=916,511 916w, https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg?resize=768,429 768w, https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg?resize=1024,572 1024w, https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg?resize=96,54 96w, https://engineering.fb.com/wp-content/uploads/2026/07/image2.jpg?resize=192,107 192w" sizes="(max-width: 992px) 100vw, 62vw" /></p> <h1><span style="font-weight: 400;">From Short-Term Fix to Strategic Asset</span></h1> <p><span style="font-weight: 400;">What started out as a targeted response to a very specific operational issue has turned out to be much more strategic, and widely applicable, than we originally anticipated. sched_ext delivers some key benefits to Meta:</span></p> <p><b>A parallel and decoupled scheduler optimization path.</b><span style="font-weight: 400;"> Upstream Linux scheduling naturally evolves over time, sometimes in larger steps (such as the CFS-to-EEVDF transition), which can be disruptive to downstream consumers. sched_ext gives Meta the flexibility to continuously improve these custom schedulers alongside that evolution. We run and refine our own BPF-based scheduling logic, tailored to the unique demands of our production workloads, so our critical services stay optimized regardless of what happens upstream.</span></p> <p><b>Independent deployment and reduced overheads.</b><span style="font-weight: 400;"> Scheduler improvements ship as BPF program updates, shipped in days rather than months. The resulting reduction in the cost of experimentation is transformative. Ideas that previously required a kernel patch and months of validation — local-cache-aware placement, ROI-based executor routing, NUMA-aware steering — become tractable iterations rather than major projects.</span></p> <p><b>A shared industry asset.</b><span style="font-weight: 400;"> sched_ext was upstreamed into Linux v6.12, so the same mechanism Meta used here is now available to the entire Linux ecosystem. Any operator with a workload that doesn&#8217;t fit the general-purpose model — hyperscaler, cloud provider, embedded systems team — can ship workload-specific scheduling policies without forking the kernel.</span></p> <h1><span style="font-weight: 400;">Future Plans</span></h1> <p><span style="font-weight: 400;">sched_ext is already allowing us to see opportunities for further improvements in ads performance, by giving the application more fine-grained control over the behavior of the scheduler. For example, the ads services have important context about the relative importance of service requests, and are potentially able to signal to the scheduler when a thread starts working on an important request. When the scheduler receives this hint, it can take appropriate steps like increasing this thread’s scheduling slice or ensuring it’s always at the top of the queue.</span></p> <h1><span style="font-weight: 400;">Acknowledgments</span></h1> <p><i><span style="font-weight: 400;">Special thanks to </span></i><i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Samuel Nair&quot;,&quot;per_e&quot;:&quot;samnair@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Samuel Nair</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Usama Arif&quot;,&quot;per_e&quot;:&quot;uarif@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Usama Arif</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;GP Musumeci&quot;,&quot;per_e&quot;:&quot;gpmusumeci@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">GP Musumeci</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Praveen Alevoor&quot;,&quot;per_e&quot;:&quot;apraveen@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Praveen Alevoor</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Ye Wang&quot;,&quot;per_e&quot;:&quot;yeye@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Ye Wang</span></i>,<i><span style="font-weight: 400;"> and the broader Ads capacity efficiency and kernel team for their contributions and collaboration.</span></i></p> <p><i><span style="font-weight: 400;">Ads Infra Leadership Team: </span></i><i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Uladzimir Pashkevich&quot;,&quot;per_e&quot;:&quot;upashkevich@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Uladzimir Pashkevich,</span></i> <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Varna Puvvada&quot;,&quot;per_e&quot;:&quot;vpuvvada@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Varna Puvvada,</span></i> <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Prabhakar Goyal&quot;,&quot;per_e&quot;:&quot;prgoyal@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Prabhakar Goyal,</span></i> <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Neeraj Agrawal&quot;,&quot;per_e&quot;:&quot;neeraja@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Neeraj Agrawal,</span></i> <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Tak Yan&quot;,&quot;per_e&quot;:&quot;tyan@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Tak Yan</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Liz Shepherd&quot;,&quot;per_e&quot;:&quot;lizshep@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Liz Shepherd</span></i>, <i><span style="font-weight: 400;" data-rich-links="{&quot;per_n&quot;:&quot;Drew Lackman&quot;,&quot;per_e&quot;:&quot;lackmana@meta.com&quot;,&quot;type&quot;:&quot;person&quot;}">Drew Lackman</span></i></p> <footer class="blockquote-footer"><span style="font-weight: 400;"><sup>1</sup> Measured at the ads serving platform entry point across all monetized surfaces. Independently verified on June 22, 2026: 464 billion requests over 24 hours window (≈5.4M req/s on average).</span></footer> <footer class="blockquote-footer"><span style="font-weight: 400;"><sup>2</sup> Figures are from the initial launch on Meta&#8217;s largest ads-serving server type (AMD Bergamo hosts), switching from Linux kernel 6.4 + CFS to kernel 6.9 + sched_ext. Measured by backtest after the rollout reached global scale and stabilized (~3 weeks), and validated via the Ads Delivery launch-candidate review plus group and company holdout backtests. The 28% is the 99th percentile latency reduction on the ads-retrieval stage specifically; the 1.1% is weighted-ads-ranked metric increase, an organic effect of ranking more ads as tail latency improves; the 3.28 MegaWatts saving is derived from the 1.1% weighted-ads-ranked increase and 1.6% CPU-utilization reduction.</span></footer> <p>The post <a href="https://engineering.fb.com/2026/07/13/ml-applications/modernizing-the-meta-ads-service-with-an-open-source-kernel-scheduler/">Modernizing the Meta Ads Service With an Open-Source Kernel Scheduler</a> appeared first on <a href="https://engineering.fb.com">Engineering at Meta</a>.</p> Better tools made Copilot code review worse. Here’s how we actually improved it. - The GitHub Blog https://github.blog/?p=97467 2026-07-10T15:57:47.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Give an agent better tools and it should do better work. That&rsquo;s the instinct, anyway.</p> <p class="wp-block-paragraph">When you open a pull request, Copilot code review reads the diff and explores the surrounding code to find the problems that matter before they ship. To do that, it used its own code exploration tools. So when we swapped in the better-maintained, shared tools that power the Copilot CLI, <code>grep</code>, <code>glob</code>, and <code>view</code>, we expected a clean upgrade.</p> <p class="wp-block-paragraph">Instead, in our benchmarks, we found that the cost of reviews was higher and fewer issues were being caught.</p> <p class="wp-block-paragraph">But the tools weren&rsquo;t the problem. The instructions were. Once we rewrote them for the way a reviewer actually reads a pull request, the regression flipped into a win: <strong>roughly 20% lower average review cost</strong>, while maintaining the same review quality.</p> <p class="wp-block-paragraph">This is the story of how adjusting the workflows around the tools led us to a fix.</p> <h2 id="h-same-tools-wrong-instincts" class="wp-block-heading">Same tools, wrong instincts</h2> <p class="wp-block-paragraph">If you&rsquo;ve built on top of an agent framework, you&rsquo;ve probably inherited its tools too. They work, so you keep them, until the day your use case drifts far enough from what they were designed for that they quietly start working against you. That&rsquo;s the situation we were in. Before trying to use the shared CLI tools, Copilot code review used its own code exploration tools. That tool layer was inspired by earlier agentic systems, including ideas from <a href="https://github.com/swe-agent/swe-agent">SWE-agent-style repository navigation</a> and <a href="https://docs.github.com/en/code-security/concepts/code-scanning/copilot-autofix-for-code-scanning">GitHub Copilot Autofix</a>: list directories, search files, search directories, and read code. Those tools worked, but they were specific to Copilot code review, and they were designed for how models behaved at the time. Earlier agentic coding models made fewer tool calls and were worse at automatically pulling in necessary context. This meant it was more important to include all relevant information in the few tool calls that the model made.</p> <p class="wp-block-paragraph">Meanwhile, the Copilot CLI harness has a shared set of Unix-inspired code exploration tools: grep, glob, and view. That harness is also used by a growing number of Copilot agent products, including <a href="https://docs.github.com/copilot/concepts/agents/cloud-agent/about-cloud-agent">GitHub Copilot cloud agent</a>, so harness improvements can benefit more than one product. We wanted to clean up and share infrastructure where possible, so we experimented with using the tools from the Copilot CLI harness in Copilot code review. The goal was to reduce duplicated tool implementations, create one shared place to improve code exploration tools, and make it easier to carry those improvements across Copilot products.</p> <p class="wp-block-paragraph">On paper, the migration looked simple:</p> <figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>Old Copilot code review</strong></th><th><strong>GitHub Copilot CLI</strong></th><th><strong>Purpose</strong></th></tr></thead><tbody><tr><td>list_dir&nbsp;</td><td>glob&nbsp;</td><td>Discover candidate files and directories before opening&nbsp;code.&nbsp;</td></tr><tr><td>search_file&nbsp;and&nbsp;search_dir&nbsp;</td><td>grep&nbsp;</td><td>Search code for matching text, symbols, or call sites.&nbsp;</td></tr><tr><td>read_code&nbsp;</td><td>view&nbsp;</td><td>Read the relevant file contents once a path or range is known.&nbsp;</td></tr></tbody></table></figure> <p class="wp-block-paragraph">The existing review tools were not thin wrappers. When searching for a directory or reading a code range, they could return the matched or requested lines plus extra surrounding code context. That added token cost, but it also matched how earlier models often benefited from having nearby context included automatically.</p> <p class="wp-block-paragraph">Initially, we hoped this would be a simple migration: swap one set of tools for another. But when we tested the shared tools in offline benchmarks, the review agent became less efficient and less effective. Average cost increased, and the number of useful comments dropped.</p> <h2 id="h-the-trace-revealed-a-browsing-loop" class="wp-block-heading">The trace revealed a browsing loop</h2> <p class="wp-block-paragraph">Our internal Copilot code review benchmarks were useful because they show more than a final score. They show the path the agent took, including which tools it called, how much output came back, where errors happened, and whether it was narrowing toward evidence or widening the search.</p> <p class="wp-block-paragraph">When we first tried the shared Copilot CLI tools in offline benchmarks, the agent often behaved as if it was browsing a repository instead of investigating a pull request. It would search broadly, guess likely paths, read broadly, find more things to search, and carry that extra context forward.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="478" width="1024" src="https://github.blog/wp-content/uploads/2026/07/1.png?resize=1024%2C478" alt="Diagram showing the flow before &mdash; a simplified illustration of the general-purpose behavior we observed: widening the search, guessing paths, and accumulating context." class="wp-image-97468" srcset="https://github.blog/wp-content/uploads/2026/07/1.png?w=2400 2400w, https://github.blog/wp-content/uploads/2026/07/1.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/1.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/1.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/1.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/07/1.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">Figure 1:&nbsp;Before &mdash; a&nbsp;simplified&nbsp;illustration&nbsp;of the general-purpose&nbsp;behavior we&nbsp;observed:&nbsp;widening&nbsp;the search, guessing&nbsp;paths, and accumulating context.</figcaption></figure> <p class="wp-block-paragraph">That pattern is understandable. Broad exploration can be useful when the task is &ldquo;understand this repo.&rdquo; But it&rsquo;s not how a reviewer would usually review a pull request.</p> <p class="wp-block-paragraph">When I review a pull request, I start from the diff and ask targeted questions:</p> <ul class="wp-block-list"> <li>Where is this function called?</li> <li>Is this config key used anywhere else?</li> <li>Is there a test or helper with the same pattern?</li> <li>What is the smallest nearby code range that explains this behavior?</li> </ul> <p class="wp-block-paragraph">I do not want to open a large part of the repository before I know what I am looking for. I want the minimal context needed to answer the question, without overloading the review with unrelated code.</p> <p class="wp-block-paragraph">That matters because every tool result becomes part of the agent&rsquo;s working context. Extra file contents can be carried forward into later reasoning, increasing cost and sometimes making the review less focused. A tool result is not a disposable printout; for an agent, it&rsquo;s extra tokens that stay in the context window.</p> <p class="wp-block-paragraph">The traces made that difference visible. The shared tools were not the problem. The instructions were giving the agent the wrong instincts to do an efficient and effective review.</p> <p class="wp-block-paragraph">The tools themselves worked, but their instructions were tuned for their use within the Copilot CLI and implied the wrong workflow: the agent used grep, glob, and view like a broad coding assistant instead of a reviewer. A coding assistant may map a whole area before making a change to ensure it doesn&rsquo;t break some other corner of the code. On the other hand, a reviewer usually starts from the diff, asks whether the change introduced a problem, and then looks for the narrowest nearby evidence required to confirm or dismiss it.</p> <p class="wp-block-paragraph">General coding-assistant tool instructions, like the ones used by Copilot CLI or Copilot cloud agent, make sense for an interactive assistant. A developer may ask it to understand a repository, plan a change, edit files, and continue over multiple turns.</p> <p class="wp-block-paragraph">Copilot code review has a narrower job: start from a pull request diff, gather enough surrounding evidence to decide whether a change introduces a real issue, and avoid loading context that is not needed for that review question.</p> <p class="wp-block-paragraph">It was therefore clear that we couldn&rsquo;t simply replace the previous Copilot code review tools with the tools from the Copilot CLI without additional prompting work. The problem became: how do we design tool instructions that use these shared tools effectively in a code review setting?</p> <h2 id="h-rewriting-the-tool-instructions-for-a-reviewer-s-workflow" class="wp-block-heading">Rewriting the tool instructions for a reviewer&rsquo;s workflow</h2> <p class="wp-block-paragraph">The next iterations made the guidance specific to code review. The workflow we wanted Copilot code review to follow was:</p> <ol class="wp-block-list"> <li>Start from the diff and form specific review questions.</li> <li>Use <code>glob</code> when the path is uncertain and <code>grep</code> to find candidate files, symbols, and call sites.</li> <li>Batch cheap discovery before reading files.</li> <li>Use <code>view</code> only when the agent knows which file or line range it needs.</li> <li>Batch focused reads instead of alternating between one search and one read.</li> </ol> <p class="wp-block-paragraph">In oversimplified form, this was the behavior we encoded:</p> <p class="wp-block-paragraph"><strong>Generic posture:</strong> Use the available tools to inspect repository context that may be relevant.</p> <p class="wp-block-paragraph"><strong>Review-shaped guidance:</strong> Start from the diff. Narrow first with <code>grep</code> and <code>glob</code>; read exact evidence with <code>view</code>. If <code>grep</code> fails to find relevant context, retry with a simpler escaped search. If a path is wrong, pivot to <code>glob</code> instead of guessing nearby paths.</p> <p class="wp-block-paragraph">For example, imagine the diff changes an authorization helper that decides whether an operation is allowed. A relevant review question is not &ldquo;show me the full contents of every file that calls this helper.&rdquo; It could instead be the narrower: &ldquo;are any request-handling callers relying on the old behavior?&rdquo;</p> <p class="wp-block-paragraph">The intended path is short:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>start from the helper changed in the diff grep for callers of that helper glob for likely route, handler, or controller files view the most relevant caller ranges decide whether any caller changes the risk</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="start from the helper changed in the diff grep for callers of that helper glob for likely route, handler, or controller files view the most relevant caller ranges decide whether any caller changes the risk" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">The guidance also changed how the agent recovered from failed searches. If an input made <code>grep</code> fail, the better next step was one simpler, corrected search. If a path was wrong, the better next step was <code>glob</code>, not guessing neighboring paths and reading whatever happened to exist. That nudged the agent away from letting a small tool failure turn into a larger exploration loop.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="589" width="1024" src="https://github.blog/wp-content/uploads/2026/07/2.png?resize=1024%2C589" alt="Diagram showing the flow after: a simplified illustration of the review-shaped behavior the prompt guided toward: stay anchored to the diff, narrow with&nbsp;grep&nbsp;and&nbsp;glob, then read focused ranges with&nbsp;view." class="wp-image-97469" srcset="https://github.blog/wp-content/uploads/2026/07/2.png?w=2400 2400w, https://github.blog/wp-content/uploads/2026/07/2.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/2.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/2.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/2.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/07/2.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption">Figure 2: After &mdash; a simplified illustration of the review-shaped behavior the prompt guided toward: stay anchored to the diff, narrow with&nbsp;grep&nbsp;and&nbsp;glob, then read focused ranges with&nbsp;view.</figcaption></figure> <p class="wp-block-paragraph">The change was small in wording and large in effect. It changed the rhythm of the agent from &ldquo;browse, read, search again&rdquo; to &ldquo;ask, narrow, read, decide.&rdquo;</p> <h2 id="h-benchmarks-let-us-debug-behavior-not-just-scores" class="wp-block-heading">Benchmarks let us debug behavior, not just scores</h2> <p class="wp-block-paragraph">The shared harness gave us the tools. The internal Copilot code review benchmarks gave us the feedback loop.</p> <p class="wp-block-paragraph">We could run the same review examples, compare tool traces, update the instructions, and run again. That let us ask concrete questions:</p> <ul class="wp-block-list"> <li>Did the agent narrow first, or read broadly first?</li> <li>Did it batch independent searches?</li> <li>Did it call <code>view</code> only when it had a reason?</li> <li>Did a tool-instruction change reduce tool errors, or just move them somewhere else?</li> <li>Did the trace stay focused on evidence from the diff?</li> <li>Did the review still preserve the quality metrics we cared about?</li> </ul> <p class="wp-block-paragraph">The most useful signal was not &ldquo;the instructions are better.&rdquo; It was more concrete. The agent was making a similar number of tool calls, but spending more of them on relevant evidence instead of repeatedly expanding the search.</p> <p class="wp-block-paragraph">That connected product-level outcomes to understandable engineering behavior. Instead of guessing why a score moved, we could inspect the workflow that produced it.</p> <h2 id="h-the-result-roughly-20-lower-average-review-cost" class="wp-block-heading">The result: roughly 20% lower average review cost</h2> <p class="wp-block-paragraph">In production, the tuned behavior showed <a href="https://github.blog/changelog/2026-06-25-copilot-code-review-analysis-depth-and-efficiency-updates/#behind-the-scenes-cli-based-file-tools-in-copilot-code-review"><strong>roughly 20% lower average review cost</strong></a> compared with the control. Importantly, it did not show a quality signal that could block shipping.</p> <p class="wp-block-paragraph">The reduction did not come from the tools by themselves, it came from the workflow around them. Shared code exploration tools, Copilot code review custom tool instructions, and internal benchmarks made the agent&rsquo;s behavior visible enough to tune.</p> <p class="wp-block-paragraph">That framing matters when building with agents. It can be tempting to treat tools as implementation details by swapping one tool for another, then comparing the final answer. But for an agent, the tool surface is part of the product experience. It changes what the agent notices, how it searches, how much context it carries forward, and when it decides it has enough evidence.</p> <p class="wp-block-paragraph">Tool descriptions and system instructions are closer to API documentation. Unclear API docs can leave a developer confused and lead to inefficient or wrong decisions. Unclear tool prompting can do the same for an LLM; a small wording change can affect cost, quality, and the shape of the investigation because it changes how the agent spends its attention.</p> <h2 class="wp-block-heading" id="same-tools-different-job">Same tools, different job</h2> <p class="wp-block-paragraph">We also tried to apply the same kind of focused tool instructions in the CLI, where it did not produce the same kind of win. That is a useful counterexample, and an important guardrail for the lesson.</p> <p class="wp-block-paragraph">Copilot code review is anchored to a diff and a review question. Copilot CLI handles broader, interactive coding tasks where exploration can be part of the job. There may be no single diff anchor, the user may change direction over multiple turns, and the right context may not be obvious at the start. The same <code>grep</code>, <code>glob</code>, and <code>view</code> tools can support both products, but the workflow around those tools has to match the product.</p> <p class="wp-block-paragraph">The takeaway is that shared tools scale when the instructions and benchmarks match the job.</p> <div class="wp-block-group post-content-cta has-global-padding is-layout-constrained wp-block-group-is-layout-constrained"> <p class="wp-block-paragraph">Try it out yourself using <a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/request-a-code-review/use-code-review">GitHub Copilot code review.</a></p> </div> </body></html> <p>The post <a href="https://github.blog/ai-and-ml/github-copilot/better-tools-made-copilot-code-review-worse-heres-how-we-actually-improved-it/">Better tools made Copilot code review worse. Here&#8217;s how we actually improved it.</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> How GitHub gave every repository a durable owner - The GitHub Blog https://github.blog/?p=97373 2026-07-09T16:29:37.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">GitHub has over 14,000 repositories across our primary internal GitHub organization. As of early 2025, there were over 11,000 non-archived repositories, the vast majority of which with no clear owner. For repositories attached to production services, we have historically had robust durable ownership, but for repositories with no associated service, there was no reliable way to tell who the owner is.</p> <p class="wp-block-paragraph">That gap became a recurring problem during our <a href="https://github.blog/security/application-security/how-github-used-secret-scanning-to-reach-inbox-zero/">secret scanning remediation effort</a>: while we could technically rotate a secret, doing so without knowing the repository owner was risky and often disruptive, and we had no clear way to route remediation work. Over the course of a month and a half, we validated ownership for every active repository, archived about 8,000 repositories that were no longer in use, and changed repository creation so that ownership was required from the start.</p> <h2 id="h-our-original-ownership-model" class="wp-block-heading">Our original ownership model</h2> <p class="wp-block-paragraph">For years, GitHub has been tracking ownership for deployed services through our internal Service Catalog. Each service entry recorded metadata like which repository it lived in, which gave us a mapping from service to repository; the owning team; executive sponsor; and support information.</p> <p class="wp-block-paragraph">Here&rsquo;s an example of the Repo Ownership app&rsquo;s service ownership entry:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>- team: github/repo-ownership-dev repo: https://github.com/github/repo-ownership name: repo-ownership kind: moda long_name: Repo Ownership description: Service enforcing repo ownership across the org maintainer: mrecachinas exec_sponsor: stephanmiehe ... </code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="- team: github/repo-ownership-dev repo: https://github.com/github/repo-ownership name: repo-ownership kind: moda long_name: Repo Ownership description: Service enforcing repo ownership across the org maintainer: mrecachinas exec_sponsor: stephanmiehe ..." tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">Having this rich metadata enables service-centric workflows, such as incident response, on-call routing, vulnerability management, and compliance scoping.</p> <p class="wp-block-paragraph">Unfortunately, that relationship was many-to-one (i.e., a service could only be attached to a single repository, but a single repository could have multiple services). That meant if you started from a service, you could find the repository and its owners. But if you started from a repository and needed to find an owner, you had to reverse the lookup, and that only worked for repositories that mapped to a service in the first place.</p> <p class="wp-block-paragraph">That left a significant ownership gap that included team repositories, documentation repositories, internal tools, one-off project repositories, personal experiment repositories, and anything else that didn&rsquo;t back a deployed service. Every time we needed to contact the owner of one of these &ldquo;unowned&rdquo; repositories, it required manual work: check the commit history, read the README, ask around in Slack, or make a guess based on the repository name.</p> <p class="wp-block-paragraph">For a one-off effort, that kind of ambiguity is annoying but manageable. For recurring security workflows that fan out across the entire organization, it presents a real risk. During our secret scanning cleanup, we spent too much time trying to find the right owners before we could make informed decisions about alerts.</p> <h2 id="h-designing-the-new-ownership-model" class="wp-block-heading">Designing the new ownership model</h2> <p class="wp-block-paragraph">Fundamentally, we needed repository ownership to be a first-class property. We considered storing ownership in a dedicated file within each repository or maintaining it in a centralized repository, but ultimately chose GitHub custom properties. This approach provided a native, structured, and organization-wide queryable way to manage ownership. It also enabled us to enforce enterprise and organization policies and rulesets selectively according to ownership type.</p> <p class="wp-block-paragraph">We created two custom properties: <code>ownership-type</code> and <code>ownership-name</code>.</p> <ul class="wp-block-list"> <li><code>ownership-type</code> accepted three values: &ldquo;Service Catalog,&rdquo; &ldquo;Hubber Handle&rdquo; (a &ldquo;Hubber&rdquo; is what we call a GitHub employee), and &ldquo;Team.&rdquo; These covered the realistic range of repository ownership at GitHub. A repository either belongs to a service (with an on-call team and a defined lifecycle), a team (like a shared documentation repository or internal tool), or an individual (like a personal project or experiment).</li> <li><code>ownership-name</code> was a text field with light validation. Our GitHub App validated every value: Hubber handles were checked against actual membership in our GitHub organization, teams were verified to exist in the organization and have at least two members, and Service Catalog entries were confirmed against our Service Catalog itself. We were intentionally permissive on formatting. If someone typed <code>@my-team</code> instead of <code>my-team</code>, we accepted it. We wanted to make it frictionless to add ownership and lean on robust validation to catch invalid entries like nonexistent teams, former employees, and services that had been decommissioned.</li> </ul> <h2 id="h-day-one-coverage" class="wp-block-heading">Day-one coverage</h2> <p class="wp-block-paragraph">Before we asked anyone to do anything, we built a periodic sync from Service Catalog to repository custom properties. Every repository that backed a known service had its <code>ownership-type</code> set to &ldquo;Service Catalog&rdquo; and its <code>ownership-name</code> populated automatically. That took care of about 1,500 service-backed repositories, leaving team repos, docs repos, one-off projects, and personal repos remaining.</p> <h2 id="h-the-rollout" class="wp-block-heading">The rollout</h2> <p class="wp-block-paragraph">To roll this out, we built a GitHub App backed by a Kubernetes CronJob. The enforcement logic needed access to Service Catalog, the GitHub API, and a few internal systems, so a simple GitHub Actions workflow wasn&rsquo;t sufficient.</p> <p class="wp-block-paragraph">The diagram below shows the repository ownership enforcement flow, from initial ownership scan through warning issue creation, automatic closure, or archival after 30 days.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="1024" width="683" src="https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?resize=683%2C1024" alt="Flowchart of repository ownership enforcement: repositories are scanned for ownership, missing ownership opens a 30-day archive warning issue, unresolved issues lead to archiving, and resolved issues are closed automatically. " class="wp-image-97442" srcset="https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?w=1164 1164w, https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?w=200 200w, https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?w=683 683w, https://github.blog/wp-content/uploads/2026/07/CleanShot-2026-07-09-at-12.21.43@2x.png?w=1025 1025w" sizes="(max-width: 683px) 100vw, 683px" /></figure> <p class="wp-block-paragraph">We scheduled the first run of the CronJob for a Saturday morning thinking nobody would be paying attention&hellip; Big mistake! Issues started appearing in repositories across the organization, and people began jumping on Slack, asking about this new issue in their repository saying it would be archived. At a globally distributed company, someone is always online.</p> <p class="wp-block-paragraph">After the 30-day grace period, we archived any repository that still didn&rsquo;t have ownership set. We chose archiving because it&rsquo;s reversible and non-destructive: the repository becomes read-only and GitHub Actions stops running, but nothing is deleted. If someone needs it again, we provided an easy way for them to unarchive it, set ownership, and continue. That enabled us to safely apply archival broadly instead of debating every edge case.</p> <p class="wp-block-paragraph">Once the initial grace period passed and the bulk of archiving was done, we tightened the enforcement loop from 30 days to one hour. A new repository that somehow bypassed the creation-time ownership requirement would get flagged almost immediately.</p> <h2 id="h-the-sharp-edges" class="wp-block-heading">The sharp edges</h2> <p class="wp-block-paragraph">This mostly rolled out seamlessly, with two minor internal incidents exposing some interesting edge cases.</p> <p class="wp-block-paragraph">The first incident was caused by archiving a repository where ownership had not been applied. Datadog had been configured to open issues in that repository as part of a monitoring workflow. When the repository was archived and Datadog couldn&rsquo;t create the issue, our internal monitoring service noticed and automatically paged the owning team, and they escalated to us.</p> <p class="wp-block-paragraph">That incident exposed a gap in <em>how</em> we notified. The ownership issues were landing in repositories, but nobody was getting notified directly. We fixed this by @-mentioning repository administrators and assigning all users with write access as a fallback on ownership issues. That way the issues couldn&rsquo;t be buried or overlooked, and the people who could actually set ownership saw them immediately.</p> <p class="wp-block-paragraph">The second incident was a data reliability problem. While we were robust against a Service Catalog outage, we didn&rsquo;t consider that it might return stale data or corrupted data. If bad data caused the app to think a batch of repositories had lost their Service Catalog entries when they hadn&rsquo;t, we&rsquo;d be mass-archiving repositories with perfectly valid owners.</p> <p class="wp-block-paragraph">To mitigate the risk of archiving legitimate repositories, we added a low water mark. During each run, prior to performing any actions, the app would tally how many archives it was about to perform and issues it was about to open. If the number exceeded a conservative threshold, it would bail out entirely and trigger a Datadog monitor rather than risk a bad run. If Service Catalog was unreachable, the job would skip Service Catalog validation and only check what it could verify independently.</p> <h2 id="h-results-by-the-numbers" class="wp-block-heading">Results, by the numbers</h2> <p class="wp-block-paragraph">We finished with approximately 3,000 active repositories and 11,000 archived (up from about 3,000 archived at the start). The entire effort took under 45 days from the first (Saturday morning) run to steady state. Every active repository now has a validated owner, or it gets archived.</p> <p class="wp-block-paragraph">Many of those newly archived repositories hadn&rsquo;t seen a commit in years: abandoned experiments, completed hackathon projects, and even one-person prototypes from 2008! Archiving them ultimately reduced our surface area and made the active repository inventory reflect reality.</p> <h2 id="h-making-ownership-stick" class="wp-block-heading">Making ownership stick</h2> <p class="wp-block-paragraph">Getting to 100% coverage is only useful if it stays at 100%, so we enforced ownership properties across every repository creation workflow, including the repository creation page (shown below), internal tooling and automation, making them mandatory for all new repositories.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="962" height="853" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-09-at-9.03.06-AM.png?resize=962%2C853" alt="GitHub repository creation page showing required ownership custom property fields." class="wp-image-97440" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-09-at-9.03.06-AM.png?w=962 962w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-09-at-9.03.06-AM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-09-at-9.03.06-AM.png?w=768 768w" sizes="(max-width: 962px) 100vw, 962px" /></figure> <p class="wp-block-paragraph">We also tightened the enforcement loop: repositories that lose their ownership are now flagged within one hour rather than the original 30-day grace period.</p> <p class="wp-block-paragraph">Each ownership type has its own durability characteristics, and we designed around them. Service Catalog entries follow service lifecycle: when a service is deprecated, its repositories typically get archived too, and that&rsquo;s the intended behavior. Teams are validated for having at least one member, and team existence and membership tend to be reasonably stable. Individual Hubber handles only become invalid when someone leaves the company, which usually means their personal repositories should be archived regardless. For any repository critical enough to outlast a single person, ownership should be a team or a service, not an individual.</p> <h2 id="h-what-this-means-for-you" class="wp-block-heading">What this means for you</h2> <p class="wp-block-paragraph">You can implement a similar ownership model today using GitHub custom properties. Here&rsquo;s the approach we&rsquo;d recommend:</p> <ul class="wp-block-list"> <li><strong>Define your ownership taxonomy.</strong> Decide which types of owners make sense for your organization. Services, teams, and individuals worked for us, but your categories might look different.</li> <li><strong>Create custom properties at the organization level.</strong> Set up an <code>ownership-type</code> property as a single-select with your allowed values, and an <code>ownership-name</code> property as text. Custom properties are queryable through the API and visible across the organization.</li> <li><strong>If you have a service catalog or asset inventory, sync it.</strong> Populating ownership for repositories you already track is the fastest way to get meaningful coverage before you start asking people to fill in gaps.</li> <li><strong>Enforce ownership</strong> <strong>at</strong> <strong>repository creation</strong> <strong>time</strong><strong>.</strong> Make the properties required so the inventory stays clean going forward.</li> <li><strong>Build a grace-period workflow for existing repositories.</strong> Open issues with a reasonable deadline (we used 30 days), then archive repositories that go unclaimed. Because archiving is reversible and non-destructive, it&rsquo;s a safe default.</li> <li><strong>Don&rsquo;t run your first enforcement pass on a Saturday!</strong></li> <li><strong>Build guardrails before you trust automation at</strong> <strong>scale</strong><strong>.</strong> The low water mark and the @-mention fallbacks weren&rsquo;t in the original design. They came from real incidents. If you&rsquo;re building a system that archives repositories or opens issues at scale, assume your data sources will occasionally be wrong, and your notifications will sometimes get lost. Design for that from the start.</li> </ul> <div class="wp-block-group post-content-cta has-global-padding is-layout-constrained wp-block-group-is-layout-constrained"> <p class="wp-block-paragraph">For more on custom properties, see the <a href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">custom properties documentation</a>.</p> </div> </body></html> <p>The post <a href="https://github.blog/security/application-security/how-github-gave-every-repository-a-durable-owner/">How GitHub gave every repository a durable owner</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Automating cross-repo documentation with GitHub Agentic Workflows - The GitHub Blog https://github.blog/?p=97282 2026-07-08T21:11:56.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">&ldquo;Where are the docs?&rdquo; It&rsquo;s a question nobody on a product team enjoys answering. The honest reply is usually some variant of &ldquo;behind.&rdquo; A writer is staring at a closed pull request, trying to reverse-engineer what changed. The pull request&rsquo;s author has already moved on. By the time the doc actually publishes, the feature has shipped, sometimes more than once.</p> <p class="wp-block-paragraph">That used to be us on the <a href="http://aspire.dev/">Aspire</a> team (we&rsquo;re a small team of 10 building dev tools for distributed apps). A few months back, we were trying to figure out how to safely bring AI into automations we already trusted. That&rsquo;s when we discovered GitHub Agentic Workflows. I started bolting prototypes into <code>microsoft/aspire</code>.</p> <p class="wp-block-paragraph">Here&rsquo;s what that bought us, in numbers pulled straight out of GitHub: for Aspire 13.3 and 13.4, <strong>82 feature-docs pull requests merged at a median of 44.8 hours after the product pull request</strong>, every one of them reviewed by the engineer who shipped the feature. No new headcount. No process retraining. Just a different way of asking &ldquo;who writes this?&rdquo;</p> <h2 id="h-the-constraint-cross-repo-automation-is-the-hard-part" class="wp-block-heading">&#128274; The constraint: cross-repo automation is the hard part</h2> <p class="wp-block-paragraph">Our product lives in <code>microsoft/aspire</code> and our docs site lives in <code>microsoft/aspire.dev</code>&mdash;different repo, deploy target, and review chain. Most teams figure out same-repo automation pretty quickly; cross-repo automation is where things get sharp. Broad repo-scoped tokens belong in a museum, and any responsible security posture (ours included) restricts them accordingly. That&rsquo;s a good thing. It&rsquo;s also a real bottleneck if the place where you write the docs isn&rsquo;t the place where you write the code.</p> <p class="wp-block-paragraph">The default workflow for years was:</p> <ol class="wp-block-list"> <li>Engineer ships a feature in <code>microsoft/aspire</code>.</li> <li>Docs writer notices weeks later.</li> <li>Docs writer opens the pull request, reads the diff, and pings the engineer to clarify what changed.</li> <li>Engineer is on the next feature, vaguely remembers, replies with half the picture.</li> <li>Docs draft ships, sometimes against a release that&rsquo;s already out.</li> </ol> <p class="wp-block-paragraph">This is the reverse-engineering tax. We needed automation that crossed repos without handing an agent a write-everywhere token. GitHub Agentic Workflows turned out to be the answer.</p> <h2 id="h-why-github-agentic-workflows" class="wp-block-heading">&#129302; Why GitHub Agentic Workflows</h2> <p class="wp-block-paragraph">GitHub Agentic Workflows is a project from the GitHub Next team that I keep describing to people as &ldquo;GitHub Actions, but with a model as the work-item processor and guard rails that satisfy security review.&rdquo; That&rsquo;s reductive, but it&rsquo;s close.</p> <p class="wp-block-paragraph">The shape of it:</p> <ul class="wp-block-list"> <li>You author a workflow as a <strong>single markdown file</strong> (<code>.github/workflows/my-thing.md</code>). YAML-style frontmatter on top, an English-language prompt underneath.</li> <li>You run GitHub Agentic Workflows compile, and it generates a sibling <code>.lock.yml</code> (a normal GitHub Actions workflow) that you commit alongside.</li> <li>At runtime, the workflow runs an agent against your prompt with a constrained toolset.</li> <li>Critically, <strong>the agent doesn&rsquo;t write to GitHub directly</strong>. It emits intent (a JSON blob describing the pull requests, issues, and comments it wants to create), and a separate, narrowly scoped job (the <strong>safe-outputs handler</strong>) materializes that intent against a per-workflow GitHub app.</li> </ul> <p class="wp-block-paragraph">That last bullet is the unlock. The agent gets read access and a prompt. Writes go through a tiny verifiable pipeline with explicit allow-lists. Security review nods. We ship.</p> <h2 id="h-a-small-aside-kindred-stacks" class="wp-block-heading">&#128154; A small aside: kindred stacks</h2> <p class="wp-block-paragraph">I love when the tools you&rsquo;re using to build are built with the same tools you&rsquo;re using to build with. The <a href="https://gh.io/gh-aw">GitHub Agentic Workflows docs</a> are built with Astro and Starlight. So is aspire.dev&mdash;Astro with Starlight, dressed up with the wider Starlight plugin ecosystem (astro-mermaid, starlight-llms-txt, starlight-sidebar-topics, starlight-image-zoom, the gorgeous @catppuccin/starlight theme, and more. Shout-out to Chris Swithinbank and the Starlight maintainers, the entire ecosystem feels designed by people who genuinely care).</p> <p class="wp-block-paragraph">There&rsquo;s a real kinship there. The tool we use to automate docs and the docs site we automate into share the same foundation. Convenient, because the Mermaid sequence diagram in the next section renders the exact same way in both worlds.</p> <h2 id="h-the-end-to-end-pipeline" class="wp-block-heading">The end-to-end pipeline</h2> <p class="wp-block-paragraph">Here&rsquo;s the flow we landed on. The protagonist is a workflow called <code>pr-docs-check.md</code> living in <code>microsoft/aspire</code>.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="355" width="1024" src="https://github.blog/wp-content/uploads/2026/07/diagram.png?resize=1024%2C355" alt="Sequence diagram showing an automated docs workflow: merging a feature pull request in microsoft/aspire triggers a GitHub Actions check that has an agent draft the documentation, open a draft pull request in microsoft/aspire.dev, and request SME review&mdash;so docs ship with the feature." class="wp-image-97406" srcset="https://github.blog/wp-content/uploads/2026/07/diagram.png?w=4594 4594w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=2048 2048w, https://github.blog/wp-content/uploads/2026/07/diagram.png?w=3000 3000w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">A run starts on <code>pull_request: closed</code> against <code>main</code> or <code>release/*</code>, gated by <code>merged == true</code>. From there, the workflow first runs a deterministic target branch resolver in plain bash before the agent ever wakes up:</p> <ol class="wp-block-list"> <li>Pull request milestone title (e.g. 13.4 &rarr; release/13.4 on <code>aspire.dev</code>).</li> <li>Linked-issue milestone title (parse Fixes/Closes/Resolves #N from the body, fetch each issue, take the first non-empty milestone).</li> <li>Pull request base ref, if it matches release/X.Y[.Z].</li> <li>Fall back to main.</li> </ol> <p class="wp-block-paragraph">This is the linchpin. <strong>Milestones in the product repo map cleanly to release branches in the docs</strong> repo. When the agent finally runs, it knows exactly where the docs should land without any creative writing about target branches or guessing.</p> <p class="wp-block-paragraph">The agent reads the diff, scans linked issues, and decides: does this need docs? If yes, it drafts the actual content in a checked-out <code>microsoft/aspire.dev</code> workspace, following our existing doc-writer skill (voice, MDX conventions, Starlight components). It then emits a <code>create_pull_request</code> safe-output and hands off.</p> <p class="wp-block-paragraph">The safe-outputs handler takes over:</p> <ul class="wp-block-list"> <li>Title prefix: [docs]</li> <li>Label: docs-from-code</li> <li>draft: true (we never auto-merge)</li> <li>Base branch: agent-supplied, restricted to <code>main</code> or <code>release/*</code></li> <li>Target repo: <code>microsoft/aspire.dev</code></li> <li>Reviewer: the <strong>SME identified from the source pull request</strong>&rsquo;s reviews&mdash;i.e., whoever the product team trusted to approve the feature, now gets asked to approve the doc for that feature.</li> </ul> <p class="wp-block-paragraph">A companion job posts a marker comment back on the source pull request with the docs pull request link and minimizes any older <code>pr-docs-check</code> comments on re-run. The engineer who just hit <strong>Merge</strong> gets a notification within a few minutes: &ldquo;Here&rsquo;s the docs draft. Look it over?&rdquo;</p> <h2 id="h-the-safe-outputs-contract" class="wp-block-heading">&#128272; The safe-outputs contract</h2> <p class="wp-block-paragraph">The whole security story comes down to a small, boring stretch of frontmatter:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>tools: github: toolsets: [repos, issues, pull_requests] min-integrity: approved # only run pinned, integrity-checked actions allowed-repos: - microsoft/* github-app: app-id: ${{ secrets.ASPIRE_BOT_APP_ID }} private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} owner: "microsoft" repositories: ["aspire.dev", "aspire"] safe-outputs: create-pull-request: title-prefix: "[docs] " labels: [docs-from-code] draft: true # human-in-the-loop, always base-branch: main allowed-base-branches: [main, release/*] target-repo: "microsoft/aspire.dev" protected-files: blocked # AGENTS.md, manifests, security config: hands off fallback-as-issue: true </code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='tools: github: toolsets: [repos, issues, pull_requests] min-integrity: approved # only run pinned, integrity-checked actions allowed-repos: - microsoft/* github-app: app-id: ${{ secrets.ASPIRE_BOT_APP_ID }} private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }} owner: "microsoft" repositories: ["aspire.dev", "aspire"] safe-outputs: create-pull-request: title-prefix: "[docs] " labels: [docs-from-code] draft: true # human-in-the-loop, always base-branch: main allowed-base-branches: [main, release/*] target-repo: "microsoft/aspire.dev" protected-files: blocked # AGENTS.md, manifests, security config: hands off fallback-as-issue: true' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">That&rsquo;s the deal in plain text. The agent gets a GitHub App token whose installation is scoped to <strong>exactly two repositories</strong>&mdash;the product repo and the docs repo&mdash;and nothing else in the org is reachable. It can only land pull requests against <code>main</code> or <code>release/*</code>. <code>AGENTS.md</code> and dependency manifests are off-limits by policy. If the pull request creation fails (network blip, conflict, anything), the framework falls back to filing an issue, so nothing is silently dropped.</p> <p class="wp-block-paragraph">This is the part security review actually liked. The agent&rsquo;s reasoning is fuzzy. The action surface is not.</p> <h2 id="h-by-the-numbers" class="wp-block-heading">&#128202; By the numbers</h2> <p class="wp-block-paragraph">Here are the stats from a rolling 30-day window (<strong>May 3 &ndash; June 2, 2026</strong>) spanning the back end of the Aspire 13.3 release and the run-up to 13.4:</p> <figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>Metric</strong>&nbsp;</th><th><strong>Value</strong>&nbsp;</th></tr></thead><tbody><tr><td>Product pull requests merged in&nbsp;microsoft/aspire&nbsp;</td><td>396 (338 main / 50 release/13.3 / 8 release/13.2)&nbsp;</td></tr><tr><td>pr-docs-check workflow runs&nbsp;</td><td>396&nbsp;</td></tr><tr><td>Draft docs pull requests created on&nbsp;microsoft/aspire.dev&nbsp;</td><td>82&nbsp;</td></tr><tr><td>&nbsp; &ndash; Merged&nbsp;</td><td>82 (100%)&nbsp;</td></tr><tr><td>&nbsp; &ndash; Closed without merge&nbsp;</td><td>0&nbsp;</td></tr><tr><td>&nbsp; &ndash; Still open&nbsp;</td><td>0&nbsp;</td></tr><tr><td>Docs pull requests target branches&nbsp;</td><td>52&nbsp;&rarr;&nbsp;release/13.3, 27&nbsp;&rarr;&nbsp;release/13.4, 3&nbsp;&rarr;&nbsp;main&nbsp;</td></tr><tr><td>Median time-to-merge (docs)&nbsp;</td><td>44.8 hours&nbsp;</td></tr><tr><td>Merged within 24 h / 7 days&nbsp;</td><td>38% / 96%&nbsp;</td></tr></tbody></table></figure> <p class="wp-block-paragraph"><em>Note: <em>Numbers captured at the time of writing; the workflows keep running, so the totals only go up.</em>&nbsp;</em></p> <p class="wp-block-paragraph">A few of those numbers deserve a second look:</p> <ul class="wp-block-list"> <li><strong>396 runs</strong> &rarr; 82 pull requests is not a defect. The workflow runs on every merged pull request; most of them are internal refactors, test fixes, or dependency bumps with no user-facing surface. The agent saying &ldquo;no docs needed&rdquo; 300+ times is a feature.</li> <li><strong>100% merge rate</strong> says the agent&rsquo;s docs picks are right. The tighter prompt we shipped after the v1 false-positive phase is paying off.</li> </ul> <h2 id="h-what-worked-what-didn-t" class="wp-block-heading">&#9989; What worked, <strong>&#10060;</strong> what didn<em>&rsquo;</em>t</h2> <p class="wp-block-paragraph"><strong>What worked</strong></p> <ul class="wp-block-list"> <li>&#9989; <strong>Milestone</strong> &rarr; release-branch mapping. This was the single highest-leverage choice we made. Engineers already set milestones on pull requests and issues; we got accurate target-branch routing for free.</li> <li>&#9989; <strong>Draft-only, SME-as-reviewer</strong>. The agent never merges. The engineer who shipped the feature is the one who confirms the docs are right. We&rsquo;ve stopped reverse-engineering features at the doc layer. The engineer just tells the docs draft what to say, in the place where they already are.</li> <li>&#9989; <strong>Scoped GitHub app per workflow</strong>. Each workflow gets its own app token with explicit repo and permission scopes. Security review approved. We approved too; the first time we needed to rotate keys.</li> <li>&#9989; <strong>protected-files: blocked</strong>. The agent cannot touch <code>AGENTS.md</code>, package manifests, or repo security config. Period.</li> </ul> <p class="wp-block-paragraph"><strong>What didn&rsquo;t (at first)</strong></p> <ul class="wp-block-list"> <li>&#10060; The agent&rsquo;s &ldquo;is this docs-worthy?&rdquo; gate was too generous in the first version. It drafted pull requests for changes that were genuinely internal, such as a CI tweak or a logging refactor. The result: 9 closures of 69 pull requests (&asymp;13%), so we tightened the prompt&rsquo;s user-facing-change definition and added explicit negative examples (CI, internal helpers, tests-only). Now, the rate is trending down.</li> <li>&#10060; Cross-repo pull request creation needed a <strong>mirrored checkout pattern</strong> that wasn&rsquo;t obvious from the docs. The agent works in one repo; safe-outputs needs to find the target repo to push a branch. We solved it by checking out <code>microsoft/aspire.dev</code> twice&mdash;once as the current workspace, once <code>under _repos/aspire.dev</code>&mdash;so the safe-outputs handler can rediscover it deterministically.</li> <li>&#10060; Big diffs blow prompt budgets. We pre-extract pull request metadata (linked issues, milestone, base ref) in pre-agent-steps bash, so the agent gets a small, structured summary instead of a giant payload. This is GitHub Agentic Workflow&rsquo;s designed-in pattern, and it works.</li> </ul> <h2 id="h-wrapping-up" class="wp-block-heading">Wrapping up</h2> <p class="wp-block-paragraph">The changes we made shifted our thinking. A feature wasn&rsquo;t considered done until the docs were. Docs no longer trail along behind it like a tin can on a string. The engineer&rsquo;s review is the gate; the bot does the typing.</p> <p class="wp-block-paragraph">Critically, <strong>this doesn&rsquo;t replace docs writers</strong>; it un-burdens them. Our writers used to spend most of their time reverse-engineering features. Now they spend their time on the things only a human can do well: narrative pages, sample programs, conceptual walkthroughs, the parts of the docs that don&rsquo;t fall out of a diff. The bot handles the mechanical &ldquo;this new option was added; here&rsquo;s the reference page update&rdquo; work that was never enjoyable for anyone.</p> <p class="wp-block-paragraph">Huge thanks to the GitHub Next team for GitHub Agentic Workflows (and for making the safe-outputs primitive a first-class part of the design), and to Chris Swithinbank and the Starlight maintainers for the docs platform we automate into. A genuine thank-you, too, to the security folks whose guardrails forced us to design this the right way the first time. The boring secret of good automation is that strong security constraints make the system more trustworthy and more correct.</p> <p class="wp-block-paragraph">If you build a product in one repo and ship docs in another&mdash;and especially if you have to do it inside any nontrivial security boundary&mdash;GitHub Agentic Workflows is worth a serious look. Start with one workflow, such as <code>pr-docs-check</code>, and watch what happens to your median time-to-docs.</p> <h2 id="h-the-other-workflows" class="wp-block-heading">&#128279; The other workflows</h2> <p class="wp-block-paragraph"><code>pr-docs-check</code> is the one I wrote this post about, but it&rsquo;s not running alone. If you&rsquo;re curious about the rest, the source is public:</p> <ul class="wp-block-list"> <li><code>milestone-changelog.md</code>: runs every two hours, picks up newly merged pull requests in the active milestone, and maintains a 13.x-Change-log wiki page (new features, improvements, notable bug fixes) with a companion editorial-feedback issue. <strong>346 runs.</strong></li> <li><code>release-update-support-mdx.md</code>: on a stable Aspire release, drafts a [support] pull request on <code>aspire.dev</code> that updates the support policy page (promotes the new version, demotes the previous one, refreshes the &ldquo;Last updated&rdquo; badge).</li> <li><code>update-integration-data.md</code>: lives in the docs repo; runs pnpm update:all daily, refreshes NuGet metadata + GitHub stats + sample data, and opens a chore: Update integration data PR with supersede-and-close logic for stale runs. <strong>27 runs, eight merged pull requests.</strong></li> <li><code>repo-pulse.md</code>: a rolling three-day repo dashboard pinned to a single issue and updated in place: recent merges, pull requests awaiting review, new issues, discussion activity. One issue, always fresh.</li> </ul> <p class="wp-block-paragraph">Happy automating, friends! &#129302;&#128640;</p> </body></html> <p>The post <a href="https://github.blog/ai-and-ml/github-copilot/automating-cross-repo-documentation-with-github-agentic-workflows/">Automating cross-repo documentation with GitHub Agentic Workflows</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> GitHub availability report: June 2026 - The GitHub Blog https://github.blog/?p=97408 2026-07-08T19:35:51.000Z <p class="wp-block-paragraph">Last month, we <a href="https://github.blog/news-insights/company-news/github-availability-report-may-2026/">added a new section</a> to our monthly reports with updates on GitHub&#8217;s availability work and the infrastructure investments behind it. The response and the questions we heard made one thing clear: customers want more of this, not less—including when the news is mixed.</p> <p class="wp-block-paragraph"><strong>The short version of June:</strong> We made real structural progress, we paused deliberately when a ramp went sideways, and we missed a target that we&#8217;ve now re-baselined.</p> <p class="wp-block-paragraph">Monolith traffic in Azure peaked at <strong>45% in Central US</strong> this month. That number is lower than we&#8217;d hoped, because we paused the ramp for roughly a month after a stability incident on May 21 made it clear the environment wasn&#8217;t ready for more traffic. We restarted the ramp on June 17 with a new per-turnup stability gate that requires the environment to be verifiably healthy before each step up. Going more slowly with more confidence is the right trade—a controlled pause is preferable to relearning the same lessons at higher load.</p> <p class="wp-block-paragraph">Git in Azure grew from 30% to a peak of <strong>43% (HTTP and SSH combined)</strong> over the month, and we missed our June target of 50%. We expect Git to plateau near 45% for now because of two deliberate decisions to avoid added user latency: we are waiting on additional vPoP traffic to route to Central US rather than backhauling IAD HUB Git traffic, and we are routing only HTTP for now because SSH has no read/write split at the edge. We continue to prioritize this, but we don’t have a new target to share. We’ll keep working as quickly and safely as possible and will report the specific numbers in our next update.</p> <p class="wp-block-paragraph">Underneath those headline numbers, we made progress that we are particularly excited about. Our new extracted pull requests service, pullsd, is now handling <strong>100% of anonymous pull request reads</strong> in production; this traffic is no longer served by the monolith. Reposd, our new extracted repository service, became the first extracted service to serve production REST traffic from Azure, ramping to 50% of read traffic before we proactively turned it down for a Redis capacity constraint. There was no incident and no rollback under duress. It will re-ramp once that capacity work completes. Our new users service is now offloading roughly <strong>500,000 queries per second at peak</strong> from our primary database, with the physical migration of authentication and authorization tables landing in early July. Our API rate limiting is now approximately <strong>97% handled at the Gateway</strong>, so rate-limiting decisions no longer contend with request-serving workers inside the monolith. Client-side database load shedding is running against 5% of real production traffic, which means we now have live evidence that we can shed low-priority queries under stress before they cascade into user-facing failures. And two-person confirmation is now required end-to-end for interactive production access and ChatOps changes, with a unified audit trail behind it.</p> <p class="wp-block-paragraph">The incident write-ups that follow are the other half of the picture: what the system did well and what it didn&#8217;t, what we&#8217;ve already changed as a result, and what we&#8217;re still changing. The same principle continues to guide us: <strong>availability, then capacity, then features.</strong></p> <hr class="wp-block-separator has-alpha-channel-opacity"/> <p class="wp-block-paragraph">In June, we experienced six incidents that resulted in degraded performance across GitHub services.</p> <p class="wp-block-paragraph"><strong>June 04 17:30 UTC (lasting 1 hour and 25 minutes)</strong></p> <p class="wp-block-paragraph">On June 4, 2026, from 17:30 to 18:55 UTC, Copilot code review experienced elevated failures for review requests on github.com. Affected users saw “Copilot ran into an error” on pull requests when requesting a code review.</p> <p class="wp-block-paragraph">During the incident window, an average of 81.6% of Copilot code review requests failed, with a peak failure rate of 93.9%, and a total of approximately 36,800 code review requests failing. GitHub Enterprise Cloud with data residency was not impacted.</p> <p class="wp-block-paragraph">The issue was caused by a newly released dependency used by the Copilot code review processing workflow. The release introduced an incompatibility with the runtime environment. Because the workflow automatically consumed the latest release, the incompatible version was picked up without sufficient compatibility validation and caused review processing to fail. Affected review jobs did not fail fast; many continued running until they timed out.</p> <p class="wp-block-paragraph">We mitigated the incident by removing the problematic dependency version and redeploying the affected processing service. New code reviews began recovering at 18:44 UTC, and the failure rate returned to baseline by 18:55 UTC. Remaining timed-out work drained by 19:59 UTC.</p> <p class="wp-block-paragraph">To reduce the risk of recurrence, we are pinning the dependency version instead of automatically consuming the latest release, adding compatibility checks for future releases, improving fast-failure behavior when the review processor cannot start, adding shorter timeout controls for review workflows, and improving monitoring for review completion failures.</p> <p class="wp-block-paragraph"><strong>June 08 06:30 UTC (lasting 2 hour and 06 minutes)</strong></p> <p class="wp-block-paragraph">On June 8, 2026, between approximately 06:30 and 08:36 UTC, signed-out users experienced sustained elevated HTTP 504 errors when accessing pull requests, issues, releases, patch diffs, and other related github.com pages. During the incident, approximately 17% of unauthenticated requests to the affected github.com endpoints returned gateway timeout errors, peaking at roughly 34% of requests at around 06:50 UTC. Some GitHub Actions workflows were also affected when they depended on release downloads or related github.com endpoints. The impact lasted approximately two hours and was isolated to unauthenticated traffic; signed-in users were not affected.</p> <p class="wp-block-paragraph">The issue was caused by a significant increase in abusive, automated anonymous traffic to specific github.com endpoints. Because unauthenticated requests are served by a dedicated pool of web application servers, it degraded our ability to respond to unauthenticated requests, causing requests to queue beyond timeout thresholds and return gateway timeout errors.</p> <p class="wp-block-paragraph">We mitigated the incident by identifying the anomalous traffic pattern and applying targeted blocks at the load balancer and application layers. Once the blocks took full effect, error rates returned to normal and affected services were fully restored by 08:36 UTC.</p> <p class="wp-block-paragraph">To reduce the likelihood and impact of similar incidents in the future, we are improving automated detection and blocking for these traffic patterns, improving our emergency traffic-blocking deployment path, and evaluating routing changes for endpoints used by both signed-out users and automated workflows.</p> <p class="wp-block-paragraph"><strong>June 10 15:05 UTC (lasting 1 hour and 20 minutes)</strong></p> <p class="wp-block-paragraph">On June 10, 2026, between 15:05 and 16:25 UTC, GitHub API services experienced degraded availability due to sporadic authentication failures affecting approximately 9% of requests. Both REST and GraphQL API requests were affected. Customers experienced intermittent “logged out” behavior as erroneous 401 (unauthorized) responses caused first- and third-party app integrations to trigger repeated authentication flows. Because only requests routed through the affected infrastructure failed, the same client could succeed on one request and fail on the next, producing the intermittent behavior. Affected requests also experienced approximately 800ms of additional latency, as the gateway retried authentication before returning an error.</p> <p class="wp-block-paragraph">A memcached proxy service, rollout to our internal API infrastructure caused our authentication service to pick up an incorrect host configuration, leading to intermittent authentication lookup failures. We mitigated the incident by deploying a configuration change to memcached service to use the correct host.</p> <p class="wp-block-paragraph">To prevent similar issues in the future, we plan to migrate our authentication system to the new caching infrastructure to improve resilience and strengthen overall reliability posture. We are also improving how the gateway distinguishes transient authentication-system errors from genuinely invalid credentials, so that temporary lookup failures no longer appear to users as being logged out.</p> <p class="wp-block-paragraph"><strong>June 16 17:20 UTC (lasting 55 minutes)</strong></p> <p class="wp-block-paragraph">On June 16, 2026, between 17:20 and 18:15 UTC, the Opus 4.8 model experienced degraded availability in GitHub Copilot. During this window, some requests to Opus 4.8 failed or errored. Other Copilot models were not affected and remained available as alternatives. This was caused by an issue with an upstream model provider.</p> <p class="wp-block-paragraph">While the issue was ongoing, we enabled degraded-mode messaging to inform affected users. The upstream provider resolved the issue, and we monitored Opus 4.8 until success rates returned to normal. The incident is fully resolved.</p> <p class="wp-block-paragraph">We are reducing our reliance on any single inference provider for a given model and balancing capacity across providers, so traffic can fail over to healthy capacity during an upstream outage. Separately, we hardened our public status-page tooling to ensure incident updates publish reliably, improving how we keep customers informed during mitigation.</p> <p class="wp-block-paragraph"><strong>June 17 03:50 UTC (lasting 54 minutes)</strong></p> <p class="wp-block-paragraph">On June 17, 2026, between approximately 03:50 and 04:44 UTC, GitHub Copilot was degraded and most of its frontier chat models were temporarily unavailable across all regions. During this window, affected models either disappeared from the model picker in the web, editor, and CLI experiences, or returned a &#8220;model not available&#8221; error when selected. Customers could continue using GitHub Copilot by selecting one of the models that remained available. The incident occurred during off-peak hours, which limited the number of customers affected.</p> <p class="wp-block-paragraph">This was due to a configuration change that our production system deemed invalid. We mitigated the incident by reverting the configuration change, after which the affected models returned automatically as the service reloaded the previous configuration.</p> <p class="wp-block-paragraph">We are working to roll out configuration changes gradually with stronger validations, alerts on sudden drops in the number of available models, and automatically roll back configuration changes that trigger these alerts.</p> <p class="wp-block-paragraph"><strong>June 25 17:33 UTC (lasting 23 minutes)</strong></p> <p class="wp-block-paragraph">On June 25, 2026, between 17:33 and 17:55 UTC, our background job service experienced degradation which increased delays to pull requests, repository pushes, actions workflows, and webhooks, with delays peaking at 7 minutes. The issue was caused by underlying hypervisor issues and an incoming traffic spike, causing service timeouts which led to a connection storm and continual rebalances.</p> <p class="wp-block-paragraph">The issue was mitigated by replacing the impacted node at 17:49, after which all services saw recovery by 18:07.</p> <p class="wp-block-paragraph">To reduce the likelihood and impact of similar incidents in the future, we have made background job processing more resilient to sudden traffic spikes, reduced the possibility of connection churn in degraded cases, removed the co-location of critical nodes so that one unhealthy node cannot affect others, and added earlier alerting on the conditions that led to this degradation.</p> <hr class="wp-block-separator has-alpha-channel-opacity"/> <p class="wp-block-paragraph">Follow our <a href="https://www.githubstatus.com/">status page</a> for real-time updates on status changes and post-incident recaps. To learn more about what we’re working on, check out the engineering section on the <a href="https://github.blog/category/engineering/">GitHub Blog</a>.</p> <p>The post <a href="https://github.blog/news-insights/company-news/github-availability-report-june-2026/">GitHub availability report: June 2026</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> How GitHub Copilot enables zero DNS configuration for GitHub Pages - The GitHub Blog https://github.blog/?p=97349 2026-07-08T16:00:00.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Custom domains make a project feel real. But for many developers, DNS, the last mile, is also the most frustrating: A records, CNAME entries, TTLs, and that long wait where you&rsquo;re never quite sure if the internet is broken or you are.</p> <p class="wp-block-paragraph">In this post, I&rsquo;ll walk through how I took a project from an empty repository to a live website on a custom domain, secured with HTTPS, in about 14 minutes without manually editing a single DNS record. The trick is to let <a href="https://github.com/features/copilot/cli">GitHub Copilot CLI</a> drive the work, with a community <a href="https://github.com/brunoborges/namecheap-skill">Namecheap skill</a> handling the DNS automation through the registrar&rsquo;s API.</p> <p class="wp-block-paragraph"><strong>Here&rsquo;s what you&rsquo;ll learn how to do:</strong></p> <ul class="wp-block-list"> <li>Publish a site with <a href="https://docs.github.com/pages">GitHub Pages</a></li> <li>Register an inexpensive domain</li> <li>Enable your registrar&rsquo;s API and connect it to Copilot CLI</li> <li>Point the domain at GitHub Pages and verify it end to end</li> </ul> <p class="wp-block-paragraph"><strong>What you&rsquo;ll need</strong></p> <ul class="wp-block-list"> <li>A GitHub account (the free tier works)</li> <li><a href="https://github.com/features/copilot/cli">GitHub Copilot CLI</a>, installed and authenticated with GitHub Copilot</li> <li>A Namecheap account, for buying the domain and using its API</li> </ul> <p class="wp-block-paragraph">No prior DNS expertise required. That&rsquo;s the whole point. Let&rsquo;s get started. &#10549;</p> <h2 class="wp-block-heading" id="step-1-publish-a-site-with-github-pages">Step 1: Publish a site with GitHub Pages</h2> <p class="wp-block-paragraph">Every deployment needs something to deploy, so start with a home for the site: a new public repository.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="721" height="266" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.14-PM.png?resize=721%2C266" alt="Screenshot of Copilot CLI screen that says 'create a public repository for this folder with the same name'" class="wp-image-97352" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.14-PM.png?w=721 721w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.14-PM.png?w=300 300w" sizes="(max-width: 721px) 100vw, 721px" /></figure> <figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="935" height="68" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.20-PM.png?resize=935%2C68" alt="Screenshot showing the public repository has been created." class="wp-image-97353" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.20-PM.png?w=935 935w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.20-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.20-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.20-PM.png?w=932 932w" sizes="(max-width: 935px) 100vw, 935px" /></figure> <p class="wp-block-paragraph">With the repository in place, you don&rsquo;t have to hand-write an <code>index.html</code>, commit it, and then click through the pages settings yourself. Instead, describe the outcome you want to Copilot CLI and let it create the landing page and enable GitHub Pages for you.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="932" height="563" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.33-PM.png?resize=932%2C563" alt="Screenshot showing a prompt to enable GitHub Pages for this repo and create a website landing page about 'GitHub Pages and Custom Domains.'" class="wp-image-97354" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.33-PM.png?w=932 932w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.33-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.42.33-PM.png?w=768 768w" sizes="(max-width: 932px) 100vw, 932px" /></figure> <p class="wp-block-paragraph">The site is now live on a github.io URL. That&rsquo;s a solid start. Now let&rsquo;s give it a proper address.</p> <h2 id="h-step-2-register-an-inexpensive-domain" class="wp-block-heading">Step 2: Register an inexpensive domain</h2> <p class="wp-block-paragraph">You don&rsquo;t need a premium .com to ship a side project. For this walkthrough I chose one of the cheapest top-level domains available, .click, and searched for an available name.</p> <p class="wp-block-paragraph"><a href="https://ghpagesblog.click">ghpagesblog.click</a> was available, so I moved to checkout.</p> <p class="wp-block-paragraph">The total came to <strong>USD $2.00</strong>, or about <strong>CAD $2.46</strong>. That&rsquo;s a low-risk price for trying a custom domain on a side project.</p> <h2 class="wp-block-heading" id="step-3-connect-the-domain-to-github-pages">Step 3: Connect the domain to GitHub Pages</h2> <p class="wp-block-paragraph">This is the step developers tend to dread. Here, an AI assistant does the repetitive work while you stay in control of the decisions.</p> <h3 id="h-enable-namecheap-api-access" class="wp-block-heading">Enable Namecheap API access</h3> <p class="wp-block-paragraph">Before Copilot CLI can update your DNS, you need to turn on Namecheap&rsquo;s API. In your Namecheap account, go to <strong>Profile &rarr; Tools</strong>, scroll to <strong>Business &amp; Dev Tools</strong>, and select <strong>Manage</strong> under <em>Namecheap API Access</em>.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="899" height="277" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.09-PM.png?resize=899%2C277" alt="Screenshot showing Profile &gt; Tools highlighted." class="wp-image-97357" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.09-PM.png?w=899 899w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.09-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.09-PM.png?w=768 768w" sizes="auto, (max-width: 899px) 100vw, 899px" /></figure> <p class="wp-block-paragraph">You can also navigate directly to the <a href="https://ap.www.namecheap.com/settings/tools/apiaccess/">API access settings page</a> (note that this URL may change over time).</p> <p class="wp-block-paragraph">On that page, complete three steps:</p> <ol class="wp-block-list"> <li>Toggle the API to <strong>ON</strong>.</li> <li>Add the public IP of the machine that will call the API to the IP allowlist (Namecheap labels this field <strong>Whitelisted IPs</strong>).</li> <li>Copy the <strong>API Key</strong> and store it somewhere safe. You&rsquo;ll need it shortly.</li> </ol> <p class="wp-block-paragraph">For more detail on what the API offers, see <a href="https://www.namecheap.com/support/api/intro/">Namecheap&rsquo;s API introduction</a>.</p> <h3 id="h-install-the-namecheap-skill" class="wp-block-heading">Install the Namecheap skill</h3> <p class="wp-block-paragraph">Next, give Copilot CLI the ability to talk to Namecheap by installing the <a href="https://awesome-copilot.github.com/skills/#file=skills%2Fnamecheap%2FSKILL.md">Namecheap skill</a>. It&rsquo;s a single command:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code"><code>gh skill install github/awesome-copilot namecheap --scope user</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="gh skill install github/awesome-copilot namecheap --scope user" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">The first time you ask Copilot to do something like <em>&ldquo;list my Namecheap domains,</em> it confirms the skill is configured and prompts you for your username.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="928" height="519" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.35-PM.png?resize=928%2C519" alt="Screenshot showing Copilot CLI prompt 'list my namecheap domains.'" class="wp-image-97358" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.35-PM.png?w=928 928w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.35-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.35-PM.png?w=768 768w" sizes="auto, (max-width: 928px) 100vw, 928px" /></figure> <p class="wp-block-paragraph">Then it asks for the API key you copied earlier.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="934" height="194" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.46-PM.png?resize=934%2C194" alt="Screenshot of Copilot asking the user 'What is your namecheap API key? It will be saved locally...'" class="wp-image-97359" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.46-PM.png?w=934 934w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.46-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.46-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.46-PM.png?w=932 932w" sizes="auto, (max-width: 934px) 100vw, 934px" /></figure> <p class="wp-block-paragraph">With credentials in place, Copilot returns the list of domains in your account. It&rsquo;s a quick way to confirm everything is wired up correctly before making any changes.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="506" height="210" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.52-PM.png?resize=506%2C210" alt="Screenshot of domains: brunoborges.io and toml-schema.org." class="wp-image-97360" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.52-PM.png?w=506 506w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.43.52-PM.png?w=300 300w" sizes="auto, (max-width: 506px) 100vw, 506px" /></figure> <h3 id="h-point-the-domain-at-github-pages" class="wp-block-heading">Point the domain at GitHub Pages</h3> <p class="wp-block-paragraph">Now connect the domain to the site. Ask Copilot to configure the custom domain using the skill.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="934" height="98" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.01-PM.png?resize=934%2C98" alt="Screenshot of a prompt asking Copilot to 'Enable this GitHub Pages site with the custom domain ghpagesblog.click registered with namecheap.'" class="wp-image-97361" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.01-PM.png?w=934 934w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.01-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.01-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.01-PM.png?w=932 932w" sizes="auto, (max-width: 934px) 100vw, 934px" /></figure> <p class="wp-block-paragraph">A good automation asks before it acts. The skill pauses to confirm the change before touching any records.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="931" height="228" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.16-PM.png?resize=931%2C228" alt="Screenshot of Copilot asking: 'The domain is using Namecheap DNS, but it currently points to Namecheap parking/redirect records. To make the apex domain work on GitHub Pages, those need to be replaced with GitHub Pages DNS records. Asking user: Replace the current Namecheap parking DNS records for ghp...'" class="wp-image-97362" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.16-PM.png?w=931 931w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.16-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.16-PM.png?w=768 768w" sizes="auto, (max-width: 931px) 100vw, 931px" /></figure> <p class="wp-block-paragraph">Once you approve, it replaces the existing parking records with the GitHub Pages A records and a CNAME for the WWW subdomain, which is the exact configuration GitHub Pages expects. This matches GitHub&rsquo;s documented steps for <a href="https://docs.github.com/pages/configuring-a-custom-domain-for-your-github-pages-site">configuring a custom domain for your GitHub Pages site</a>.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="933" height="351" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.25-PM.png?resize=933%2C351" alt="Screenshot showing 'Replace Namecheap DNS records.'" class="wp-image-97363" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.25-PM.png?w=933 933w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.25-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.25-PM.png?w=768 768w" sizes="auto, (max-width: 933px) 100vw, 933px" /></figure> <p class="wp-block-paragraph">It also handles the repository side, committing a CNAME file that tells GitHub Pages which custom domain the site should answer to.</p> <aside data-color-mode="light" data-dark-theme="dark" data-light-theme="light_dimmed" class="wp-block-group post-aside--large p-4 p-md-6 is-style-light-dimmed has-global-padding is-layout-constrained wp-block-group-is-layout-constrained is-style-light-dimmed--1" style="border-top-width:4px"> <h3 id="h-not-using-namecheap" class="wp-block-heading h5-mktg gh-aside-title is-typography-preset-h5" style="margin-top:0">Not using Namecheap?</h3> <p class="wp-block-paragraph">The same approach works with any registrar that offers an API. You don&rsquo;t need a purpose-built skill: point Copilot CLI at your registrar&rsquo;s API documentation and ask it to read, understand, and use that API to set the GitHub Pages records for your domain. The registrar changes; the workflow doesn&rsquo;t.</p> </aside> <p class="wp-block-paragraph"><strong>Not using Namecheap?</strong> The same approach works with any registrar that offers an API. You don&rsquo;t need a purpose-built skill: point Copilot CLI at your registrar&rsquo;s API documentation and ask it to read, understand, and use that API to set the GitHub Pages records for your domain. The registrar changes; the workflow doesn&rsquo;t.</p> <h2 class="wp-block-heading" id="step-4-verify-the-deployment">Step 4: Verify the deployment</h2> <p class="wp-block-paragraph">Rather than assuming success, Copilot CLI checks its own work. First, it confirms the domain resolves.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" loading="lazy" decoding="async" width="928" height="144" src="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.43-PM.png?resize=928%2C144" alt="Screenshot showing 'Verify custom domain publication (shell).'" class="wp-image-97364" srcset="https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.43-PM.png?w=928 928w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.43-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/Screenshot-2026-07-07-at-3.44.43-PM.png?w=768 768w" sizes="auto, (max-width: 928px) 100vw, 928px" /></figure> <p class="wp-block-paragraph">Then it confirms that the site returns a healthy HTTP 200 response.</p> <p class="wp-block-paragraph">If you&rsquo;d like to review every prompt and response, the <a href="https://gist.github.com/brunoborges/167c988a0c4c16b8ccffca995ae98ce2">full Copilot CLI session is available as a gist</a>.</p> <p class="wp-block-paragraph">Now for the timeline. The domain was purchased at <strong>11:21:27 a.m. ET</strong>.</p> <p class="wp-block-paragraph">The site was live on the custom domain, served over HTTPS, at around <strong>11:35 a.m. ET</strong>. That&rsquo;s roughly <strong>14 minutes</strong> from owning nothing to a fully deployed site, including API setup, skill installation, DNS configuration, propagation, and verification.</p> <h2 class="wp-block-heading" id="wrapping-up">Wrapping up</h2> <p class="wp-block-paragraph">DNS isn&rsquo;t hard, exactly, but it&rsquo;s fiddly, easy to get wrong, and slow to give feedback. By pairing GitHub Pages with GitHub Copilot CLI and the Namecheap skill, the repetitive parts of a custom-domain deployment fade into a short conversation: you make the decisions and approve the changes, and the tooling handles the plumbing.</p> <p class="wp-block-paragraph">If you&rsquo;ve been putting off a custom domain because the DNS step feels like a chore, this workflow removes the friction. To go further, explore the <a href="https://docs.github.com/pages">GitHub Pages documentation</a> and the guide to <a href="https://docs.github.com/pages/configuring-a-custom-domain-for-your-github-pages-site">configuring a custom domain for your GitHub Pages site</a>, then try it on your next project.</p> <p class="wp-block-paragraph"></p> </body></html> <p>The post <a href="https://github.blog/ai-and-ml/github-copilot/how-github-copilot-enables-zero-dns-configuration-for-github-pages/">How GitHub Copilot enables zero DNS configuration for GitHub Pages</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Q1 2026 Innovation Graph update: Open source collaboration is accelerating worldwide - The GitHub Blog https://github.blog/?p=97322 2026-07-07T16:00:00.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Q1 2026 was a banner quarter for open source collaboration, as shown by the GitHub Innovation Graph&rsquo;s <a href="https://innovationgraph.github.com/global-metrics/economy-collaborators">economy collaborators</a> metric, part of the latest data release. Outbound collaboration, defined as the sum of git pushes and pull requests sent from developers in one economy to public repositories in another economy, grew by 16% quarter-over-quarter from Q4 2025 to Q1 2026.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="500" width="1024" src="https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?resize=1024%2C500" alt="Stacked area chart of quarterly outbound collaboration among the top 30 economies, showing a steady increase from Q1 2020 to Q1 2026." class="wp-image-97324" srcset="https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=2546 2546w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_innovation_graph.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">That&rsquo;s the second highest quarter-over-quarter growth rate we&rsquo;ve seen since 2020. The highest was in Q2 2020, with a 21% growth rate, when many of us <a href="https://github.blog/news-insights/company-news/open-collaboration-on-covid-19/">suddenly and collectively decided to use our computers more</a>.</p> <p class="wp-block-paragraph">In third place was Q1 2023, with a 9% growth rate. This was the first quarter after a research lab <a href="https://openai.com/index/chatgpt/">blogged</a> about a new website they made, and they enticed users to sign up and file bug reports by offering a <a href="https://cdn.openai.com/chatgpt/chatgpt-feedback-contest.pdf">chance to win up to $500 in API credits</a>. Evidently, sweepstakes are unreasonably effective motivators.</p> <h2 id="h-metrics-by-economy" class="wp-block-heading">Metrics by economy</h2> <p class="wp-block-paragraph">While it&rsquo;s clear that collaboration is growing globally, <a href="https://github.com/github/innovationgraph/tree/main/data">plotting these and other metrics</a> separately by economy highlights the different trajectories of the world&rsquo;s developer communities:</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="780" width="1024" src="https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?resize=1024%2C780" alt="A grid of line charts showing the top 30 economies by quarterly outbound collaboration from 2020-2026. Most of the charts show increasing collaboration volume, with the European Union ranked first. " class="wp-image-97325" srcset="https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?w=1686 1686w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/outbound_collaboration_flow_q1_2026_light_mode.png?w=1536 1536w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="740" width="1024" src="https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?resize=1024%2C740" alt="A grid of line charts showing the top 30 economies by quarterly git pushes from 2020-2026. Most of the charts show increasing counts of git pushes, with the European Union ranked first. " class="wp-image-97326" srcset="https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?w=1690 1690w, https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/git_pushes_flow_q1_2026_light_mode.png?w=1536 1536w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="746" width="1024" src="https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?resize=1024%2C746" alt="A grid of line charts showing the top 30 economies by quarter-over-quarter change in repository count from 2020-2026. Most of the charts show increasing quarter-over-quarter growth of repositories, with India ranked first." class="wp-image-97327" srcset="https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?w=1684 1684w, https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?w=300 300w, https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/07/repositories_flow_q1_2026_light_mode.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">Now, it&rsquo;s your turn to analyze the <a href="https://github.com/github/innovationgraph/tree/main/data">underlying datasets</a> yourself for interesting pursuits, such as <a href="https://github.blog/news-insights/policy-news-and-insights/how-researchers-are-using-github-innovation-graph-data-to-reveal-the-digital-complexity-of-nations/">improving how economic growth is measured</a> or <a href="https://github.blog/news-insights/policy-news-and-insights/how-researchers-are-using-github-innovation-graph-data-to-estimate-the-impact-of-chatgpt/">estimating the impact of a research lab&rsquo;s new website</a>. While we have no sweepstakes to offer (for now), we think there are fascinating data stories strewn throughout these CSVs just waiting to be told.</p> <p class="wp-block-paragraph">One example might be the impressive recent growth in Syria starting in Q4 2025, which coincides with <a href="https://github.blog/company/github-is-enabling-broader-access-for-developers-in-syria-following-new-government-trade-rules/">changes we made to enable broader access to GitHub functionality following the relaxation of sanctions and export controls on the country</a>:</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="1024" width="688" src="https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?resize=688%2C1024" alt="Line charts of git pushes, developers, organizations, repositories, and outbound collaboration for Syria for Q1 2020 through Q1 2026. Most charts show increasing activity, with particularly pronounced recent growth in the number of developers in Syria. " class="wp-image-97329" srcset="https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?w=1170 1170w, https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?w=201 201w, https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?w=768 768w, https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?w=688 688w, https://github.blog/wp-content/uploads/2026/07/syria_sparklines_q1_2026_light_mode.png?w=1032 1032w" sizes="auto, (max-width: 688px) 100vw, 688px" /></figure> <p class="wp-block-paragraph">We&rsquo;re grateful to the developers who advocated for these changes and the communities that continue to help spread the word, such as <a href="https://www.gitsyria.com/">GitSyria</a>. Thanks to their efforts, we&rsquo;re happy to share that we&rsquo;ve been able to provide the <a href="https://education.github.com/pack">GitHub Student Developer Pack</a> to over 8,000 verified Syrian students in just the last six months.</p> <h2 class="wp-block-heading" id="helping-maintainers-manage-collaboration">Helping maintainers manage collaboration</h2> <p class="wp-block-paragraph">While greater collaboration leads to many benefits, we recognize that the rapid increase in contribution volume has strained several communities. Ashley Wolf, our Director of Open Source Programs, wrote about the <a href="https://github.blog/open-source/maintainers/welcome-to-the-eternal-september-of-open-source-heres-what-we-plan-to-do-for-maintainers/">Eternal September of Open Source</a> in February 2026, describing how maintainers are managing new contribution dynamics and what we&rsquo;re doing to try to help. Features we&rsquo;ve shipped include:</p> <ul class="wp-block-list"> <li><a href="https://github.blog/open-source/maintainers/how-pull-request-limits-are-cutting-down-the-noise/"><strong>Pull request limits:</strong></a> You can set a maximum number of open pull requests that users without write access may have open in your repository at one time, giving you a more proactive way to manage contribution volume.</li> <li><strong>Repo-level</strong> <a href="https://github.blog/changelog/2026-02-13-new-repository-settings-for-configuring-pull-request-access/"><strong>pull request</strong></a> <strong>and</strong> <a href="https://github.blog/changelog/2026-06-29-restrict-issue-creation-to-collaborators-only/"><strong>issue</strong></a> <strong>controls</strong>: Gives maintainers the option to limit pull request and issue creation to collaborators or disable pull requests and issues entirely.</li> <li><a href="https://github.blog/changelog/2026-02-05-pinned-comments-on-github-issues/"><strong>Pinned comments on issues</strong></a>: You can now pin a comment to the top of an issue from the comment menu.</li> <li><a href="https://github.blog/changelog/2026-02-05-pinned-comments-on-github-issues/"><strong>Banners to reduce comment noise</strong></a>: Experience fewer unnecessary notifications with a banner that encourages people to react or subscribe instead of leaving noise like &ldquo;+1&rdquo; or &ldquo;same here.&rdquo;</li> <li><a href="https://github.blog/changelog/2026-02-05-improved-pull-request-files-changed-february-5-updates/"><strong>Pull request performance improvements</strong></a>: Pull request diffs have been optimized for greater responsiveness and large pull requests in the new files changed experience respond up to 67% faster.</li> <li><a href="https://github.blog/engineering/architecture-optimization/from-latency-to-instant-modernizing-github-issues-navigation-performance/"><strong>Faster issue navigation</strong></a>: Easier bug triage thanks to significantly improved speeds when browsing and navigating issues as a maintainer.</li> <li><a href="https://docs.github.com/en/communities/moderating-comments-and-conversations/limiting-interactions-in-your-repository"><strong>Temporary interaction limits</strong></a>: You can temporarily enforce a period of limited activity for certain users on a public repository.</li> </ul> <p class="wp-block-paragraph">Do you have feedback on the directions we&rsquo;re exploring? Share it in the <a href="https://github.com/orgs/community/discussions/185387">community discussion</a>.</p> <p class="wp-block-paragraph">Share what is working for your projects, where the gaps are, and what would meaningfully improve your experience maintaining open source.</p> </body></html> <p>The post <a href="https://github.blog/news-insights/policy-news-and-insights/q1-2026-innovation-graph-update-open-source-collaboration-is-accelerating-worldwide/">Q1 2026 Innovation Graph update: Open source collaboration is accelerating worldwide</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> How GitHub used secret scanning to reach inbox zero - The GitHub Blog https://github.blog/?p=97226 2026-07-02T16:00:00.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Several years ago, GitHub Security launched an initiative to assess and improve our overall secrets hygiene. As part of that effort, we piloted the Secret Scanning capability that was under development at the time. That&rsquo;s when we found more than 20,000 secrets spread across our 15,000+ repositories.</p> <p class="wp-block-paragraph">The number was significantly higher than we anticipated, but it quickly became clear that success would depend on identifying which alerts represented real risk, assigning ownership, and remediating them safely. Nine months later, we reached zero open alerts.</p> <p class="wp-block-paragraph">New secret scanning customers often ask us: &ldquo;How do you manage this internally? How did you actually clean up your existing secrets?&rdquo;</p> <p class="wp-block-paragraph">Like many long-running software companies, GitHub&rsquo;s approach to secrets management evolved over time. GitHub was founded in 2008, before today&rsquo;s centralized vaults, automated secret scanning, and dedicated secrets-management platforms were common across the industry. As engineering practices matured and GitHub grew, we continued investing in stronger controls, better tooling, and systematic risk reduction for legacy patterns. This work reflects our ongoing commitment to improving security, reducing exposure, and ensuring our internal practices meet the same high standards we expect across the industry.</p> <p class="wp-block-paragraph">This blog post shares what worked for us during this effort, and highlights strategies you can apply to better protect your own secrets.</p> <h2 id="h-cutting-out-the-noise" class="wp-block-heading">Cutting out the noise</h2> <p class="wp-block-paragraph">The first thing we discovered was that the alert count was a bit misleading&mdash;i.e., 20,000 alerts did not mean 20,000 equally risky problems.</p> <p class="wp-block-paragraph">When we dug into the data, we discovered that just five repositories accounted for roughly 18,000 of those alerts, and every one of those secrets was inactive: test fixtures, deactivated credentials, and fake-but-valid-looking secrets used for testing. (We build secret scanning, so naturally we have repositories full of legitimate-looking secrets in tests.)</p> <p class="wp-block-paragraph">That left over 2,000 alerts that needed attention: potential live credentials and thousands of decisions about risk, rotation, and remediation.</p> <h2 id="h-secrets-don-t-just-live-in-code" class="wp-block-heading">Secrets don&rsquo;t just live in code</h2> <p class="wp-block-paragraph">Secret remediation touched more than source code. We found secrets in support tickets (customers occasionally include tokens), bug bounty reports (researchers disclose what they found with complete reproductions, including API requests with tokens used), incident notes, and wiki pages.</p> <p class="wp-block-paragraph">We partnered with customer support, security incident response, and our bug bounty program to develop shared playbooks. Across all these workflows, we had to ensure we weren&rsquo;t creating new problems, like opening issues or pushing commits containing the very secrets we were trying to remediate.</p> <h2 id="h-our-phased-approach" class="wp-block-heading">Our phased approach</h2> <p class="wp-block-paragraph">We were not going to close 20,000 alerts by asking a few security engineers to grind through them one by one. We treated it like any other operational backlog: stop new debt, then work down what already exists with a workflow that&rsquo;s repeatable, measurable, and not dependent on one person&rsquo;s institutional knowledge.</p> <h2 id="h-phase-1-enable-everywhere-stop-the-accumulation" class="wp-block-heading">Phase 1: Enable everywhere, stop the accumulation</h2> <p class="wp-block-paragraph">Before cleaning up existing secrets, we had to stop new ones from piling up.</p> <p class="wp-block-paragraph">We enabled secret scanning and push protection across all of our enterprises and organizations. Thanks to GitHub Advanced Security&rsquo;s organization-level settings, this wasn&rsquo;t a repository-by-repository slog across 15,000 repositories. We enforced the setting so individual repositories and teams could not quietly opt out.</p> <p class="wp-block-paragraph">Push protection blocked new secrets at the source. That kept the backlog from growing faster than we could burn it down.</p> <h2 id="h-phase-2-understand-and-triage" class="wp-block-heading">Phase 2: Understand and triage</h2> <p class="wp-block-paragraph">We broke down the 20,000+ alerts by repository, secret type, and age so we could separate noise from work.</p> <p class="wp-block-paragraph">When we dug in, we discovered that just five repositories accounted for roughly 18,000 of those alerts, and every one of those secrets was inactive: test fixtures, deactivated credentials, and fake-but-valid-looking secrets used for testing. (We build secret scanning, so naturally we have repositories full of legitimate-looking secrets in tests.)</p> <p class="wp-block-paragraph">For high-volume, low-risk alerts, we developed criteria for bulk closure. If a secret was in a dedicated test repository, had never been active, and matched a known test pattern, we could confidently mark it resolved. In a matter of days, we closed out roughly 18,000 alerts.</p> <h3 id="h-the-hard-questions" class="wp-block-heading">The hard questions</h3> <p class="wp-block-paragraph">We had to make strategic decisions about how to remediate secrets. When a secret lives in an issue, do you edit the body (and potentially remove revision history), or preserve the audit trail? When a secret is committed to a repository, do you rewrite git history? Anyone who&rsquo;s tried rewriting git history at scale knows what happens next: force-pushes break open pull requests, invalidate commit SHAs, and generally interrupt developers.</p> <p class="wp-block-paragraph">A common question was: &ldquo;Can we just delete the repository if it&rsquo;s no longer in use?&rdquo; Our answer was generally no. A deleted repository takes its audit trail with it. If a secret in that repository was ever leaked or the repository was ever compromised, you lose the forensic record you&rsquo;d need during incident response. Rotate the secret, archive the repository if appropriate, but keep the history.</p> <p class="wp-block-paragraph">Whenever possible, we rotate or revoke the exposed secret first. The harder question is whether the residual risk warrants rewriting git history, or whether a revoked secret in history can safely be left in place. These are the types of questions and decisions present with each alert that product security teams wrestle with.</p> <h2 id="h-phase-3-validate-what-s-actually-live" class="wp-block-heading">Phase 3: Validate what&rsquo;s actually live</h2> <p class="wp-block-paragraph">A credential sitting in a repository might have been rotated years ago, or it might still unlock production systems. You can&rsquo;t prioritize without knowing the difference.</p> <p class="wp-block-paragraph">At the time, secret scanning didn&rsquo;t have native validity checking, so we built our own approach. The goal was narrow: determine whether a credential still worked and, when appropriate, collect enough metadata to route the alert or notify the right owner.</p> <p class="wp-block-paragraph">For example, for a GitHub token, a representative check could make a single authenticated request to a low-impact endpoint like GET /user:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>response="$( curl -sS -w '\n%{http_code}' \ -H "Authorization: Bearer $TOKEN" \ -H "Accept: application/vnd.github+json" \ -H "X-GitHub-Api-Version: 2022-11-28" \ https://api.github.com/user )" status="${response##*$'\n'}" body="${response%$'\n'*}" case "$status" in 200) login="$(jq -r '.login // empty' &lt;&lt;&lt; "$body")" echo "token appears active for GitHub user: $login" ;; 401) echo "token appears invalid or revoked" ;; 403|429) echo "unable to determine validity; rate-limited or blocked" ;; *) echo "unable to determine validity: HTTP $status" ;; esac </code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="response=&quot;$( curl -sS -w '\n%{http_code}' \ -H &quot;Authorization: Bearer $TOKEN&quot; \ -H &quot;Accept: application/vnd.github+json&quot; \ -H &quot;X-GitHub-Api-Version: 2022-11-28&quot; \ https://api.github.com/user )&quot; status=&quot;${response##*$'\n'}&quot; body=&quot;${response%$'\n'*}&quot; case &quot;$status&quot; in 200) login=&quot;$(jq -r '.login // empty' &lt;&lt;&lt; &quot;$body&quot;)&quot; echo &quot;token appears active for GitHub user: $login&quot; ;; 401) echo &quot;token appears invalid or revoked&quot; ;; 403|429) echo &quot;unable to determine validity; rate-limited or blocked&quot; ;; *) echo &quot;unable to determine validity: HTTP $status&quot; ;; esac" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">Remember, our goal was to answer the smallest useful set of questions: does this credential still work, and who needs to know about it? We treated ambiguous responses as inconclusive, and we avoided follow-on requests to repositories, organizations, or other private resources.</p> <p class="wp-block-paragraph">This required close partnership with our privacy and legal teams. Even a &ldquo;read-only&rdquo; validity check can have implications when you&rsquo;re touching a credential you may not own.</p> <p class="wp-block-paragraph">As we worked through this manually, our product team built the solution natively, which made the remaining work much faster. Validity checking is now built into GitHub secret scanning.</p> <h2 id="h-phase-4-figure-out-who-owns-what" class="wp-block-heading">Phase 4: Figure out who owns what</h2> <p class="wp-block-paragraph">That cross-functional work also exposed an ownership problem: even after we knew a credential was active, we still had to figure out who could rotate it.</p> <p class="wp-block-paragraph">We partnered with customer support, security incident response, and our bug bounty program to develop shared playbooks for secrets reported outside of code. That included redacting secret values before routing work to teams, determining whether a credential belonged to GitHub or a customer, and notifying affected customers or researchers so they could rotate tokens under their control. Across all these workflows, we had to ensure we weren&rsquo;t creating new problems, like opening issues or pushing commits containing the very secrets we were trying to remediate.</p> <p class="wp-block-paragraph">For GitHub-issued credentials like personal access tokens, we worked with our product team to surface secret metadata directly in the alert: who created the token, when, and what scopes it had. That meant we didn&rsquo;t need to use the token itself to figure out who it belonged to.</p> <p class="wp-block-paragraph">For everything else, ownership was harder, and this exposed a deeper problem: not all repositories had clear owners.</p> <p class="wp-block-paragraph">Our internal engineering standards (the <a href="https://github.blog/engineering/engineering-principles/githubs-engineering-fundamentals-program-how-we-deliver-on-availability-security-and-accessibility/">Engineering Fundamentals</a> program) enforce durable ownership on services, and we maintain a mapping between services and repositories, but not all repositories map cleanly to a service. The pain we experienced led to a broader repository ownership initiative (using GitHub&rsquo;s Custom Properties), plus a parallel effort to ensure all secrets in our credential manager have durable owners. You can&rsquo;t rotate a secret if you can&rsquo;t find the owner.</p> <h2 id="h-phase-5-manual-triage-for-the-long-tail" class="wp-block-heading">Phase 5: Manual triage for the long tail</h2> <p class="wp-block-paragraph">Even with validation and metadata, a long tail of alerts required human judgment. For each one: what does this grant access to, has it been rotated, who owns the connected system, and what&rsquo;s the remediation path?</p> <p class="wp-block-paragraph">For every alert we dismissed, we ensured an accurate disposition (e.g., revoked, used in test, false positive) was recorded, along with a comment containing relevant context, such as a link to a remediation issue or an approved security exception.</p> <p class="wp-block-paragraph">This phase required close collaboration across teams to identify system owners, validate remediation status, and assess residual risk where automated signals alone were insufficient.</p> <h2 id="h-phase-6-systematize-and-drive-accountability" class="wp-block-heading">Phase 6: Systematize and drive accountability</h2> <p class="wp-block-paragraph">As patterns emerged, we made the work scalable:</p> <ul class="wp-block-list"> <li>We routed alerts into our internal <a href="https://github.blog/security/application-security/scaling-vulnerability-management-across-thousands-of-services-and-more-than-150-million-findings/">vulnerability management platform</a> for centralized tracking and reporting.</li> <li>Different credentials need different remediation steps. We documented playbooks by secret type so teams could self-serve.</li> <li>We automated notifications, routing alerts to the right teams based on repository ownership.</li> </ul> <p class="wp-block-paragraph">The final piece was accountability. We tied secret remediation to GitHub&rsquo;s <a href="https://github.blog/engineering/engineering-principles/githubs-engineering-fundamentals-program-how-we-deliver-on-availability-security-and-accessibility/">Engineering Fundamentals</a> program, making it a security fundamental that teams were measured against. We set clear expectations and gave teams visibility into status. When secret hygiene is part of how engineering health is measured, it becomes a shared responsibility across the organization.</p> <p class="wp-block-paragraph">Nine months after we started, we hit inbox zero.</p> <h2 id="h-lessons-learned" class="wp-block-heading">Lessons learned</h2> <ol class="wp-block-list"> <li><strong>Don&rsquo;t panic at the number.</strong> Our initial count was 20,000+ alerts, but 90% were not valid. The raw count is almost never the real scope of work.</li> <li><strong>Enable and enforce everywhere, no exceptions.</strong> Partial rollouts create blind spots. We enabled and enforced secret scanning and push protection at the enterprise level, without allowing anyone to opt out.</li> <li><strong>Validate before you escalate.</strong> Not every detected secret is live. Validation helps you create a prioritized to-do list.</li> <li><strong>Metadata saves hours.</strong> For GitHub credentials, secret metadata cut down the necessary detective work. If you&rsquo;re working with third-party providers, push them to surface similar metadata, or build your own enrichment layer.</li> <li><strong>You can&rsquo;t remediate without ownership.</strong> Invest in durable ownership infrastructure early.</li> <li><strong>Automate the workflow after detection.</strong> Detection gets you started, but the operational challenge was routing alerts, tracking owners, and closing the loop. Invest in the workflow layer.</li> <li><strong>Make it everyone&rsquo;s problem.</strong> Security teams can&rsquo;t remediate thousands of alerts alone. We tied secret hygiene to our Engineering Fundamentals program. When leadership watches the dashboards, teams find time to fix things.</li> <li><strong>Document your decision framework.</strong> You&rsquo;ll encounter secrets without clean remediation paths. Document how you decide: When is rotation sufficient? When do you rewrite history? When do you accept residual risk?</li> </ol> <h2 id="h-what-this-means-for-you" class="wp-block-heading">What this means for you</h2> <p class="wp-block-paragraph">You don&rsquo;t need to reinvent most of what we built. Many of our manual workarounds, including validity checking, ownership identification, and bulk triage, are now native features in secret scanning.</p> <p class="wp-block-paragraph">If you&rsquo;re starting today:</p> <ul class="wp-block-list"> <li><a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-secret-scanning">Enable and enforce secret scanning</a> and <a href="https://docs.github.com/en/code-security/secret-scanning/introduction/about-push-protection">push protection</a> everywhere.</li> <li>Triage the backlog by repository and secret type; bulk-close what you can prove is noise.</li> <li>Validate what&rsquo;s live before you escalate.</li> <li>Route alerts to owners, and track remediation like any other engineering work.</li> </ul> <p class="wp-block-paragraph">Ready to get started? <a href="https://github.com/features/security">Learn how to enable secret scanning and push protection with GitHub Advanced Security</a>.</p> <p class="wp-block-paragraph"><em>Coming soon: How we tackled repository ownership at scale, and why durable ownership of repositories and secrets is the foundation everything else depends on.</em></p> </body></html> <p>The post <a href="https://github.blog/security/application-security/how-github-used-secret-scanning-to-reach-inbox-zero/">How GitHub used secret scanning to reach inbox zero</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Meta’s AI Storage Blueprint at Scale - Engineering at Meta https://engineering.fb.com/?p=24150 2026-07-01T16:00:36.000Z <p><span style="font-weight: 400;">Over the past several years, model capabilities and training dataset sizes have experienced exponential growth. During the past year or so, the time between new-frontier-model releases has gone down from months to weeks. Reliable and fast access to storage is important to both the speed and computational cost of this AI innovation. If AI is the brain, storage is the memory: Capability and speed are highly dependent on the size of memory and speed of retrieval.  </span></p> <p><span style="font-weight: 400;">Yet while AI compute performance has roughly tripled every two years, storage and interconnect performance growth have been more modest. As a result, storage bottlenecks continue to be one of the primary contributors to GPU stalls for AI workloads, directly impacting expenditures and time to market. Aside from GPU utilization, storage architecture also directly impacts the speed of iteration in AI research; with GPUs increasingly becoming geo-distributed and dataset sizes increasingly becoming massive, researchers spend a significant amount of time ingesting and moving data across regions, thus impacting research velocity. In this blog post, we discuss how Meta&#8217;s BLOB-storage architecture evolved to address two primary challenges: maximizing GPU utilization and maximizing research velocity.</span></p> <div class="jetpack-video-wrapper"><iframe class="youtube-player" width="4000" height="2250" src="https://www.youtube.com/embed/0NZHPasMqYE?version=3&#038;rel=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;fs=1&#038;hl=en-US&#038;autohide=2&#038;wmode=transparent" allowfullscreen="true" style="border:0;" sandbox="allow-scripts allow-same-origin allow-popups allow-presentation allow-popups-to-escape-sandbox"></iframe></div> <h2><span style="font-weight: 400;">Storage Architecture Overview</span></h2> <p><span style="font-weight: 400;">Meta operates hundreds of exabyte-scale storage clusters that serve all of Meta’s external and internal products, including Facebook, Instagram, Reality Labs, Meta AI, Ads, Data Warehouse, and internal Databases. Our storage service exposes object storage, file systems, and block-device APIs, and these API abstractions are built on top of a horizontally scalable foundational block layer called Tectonic. The Tectonic layer is a regional, multi-tenant storage fabric that provides high durability and availability leveraging erasure-coding techniques, supports tiering across media types (e.g., HDD and flash), and manages smart placement of hot, cold, and warm data for efficient utilization of I/O across tenants. The BLOB-storage layers that operate on top of Tectonic expose a global, infinitely scalable storage fabric, and expose policies that let users make tradeoffs between durability and availability.</span></p> <p><span style="font-weight: 400;">In a previous </span><a href="https://atscaleconference.com/videos/training-llama-a-storage-perspective/" target="_blank" rel="noopener"><span style="font-weight: 400;">@Scale talk titled, “Training Llama: A Storage Perspective,”</span></a><span style="font-weight: 400;"> we discussed how Meta trained Llama directly over the Tectonic block layer by exposing an NFS-like FileSystem interface on top of it. While this architecture continues to be used widely within Meta, our modern training stack has been migrating slowly on top of the BLOB-storage interface, as is the case across the industry. This transition is motivated by the need for unified storage access to massive data lakes in the BLOB-storage layer as well as the need for high performance.</span></p> <h2><span style="font-weight: 400;">Maximizing GPU Utilization</span></h2> <p><span style="font-weight: 400;">Modern AI workloads are “data hungry” and have very different workload characteristics than traditional web applications: bursty and sustained high throughput, predictable and bounded pMax latencies, and variable I/O patterns. The focus for BLOB storage, in recent years, has largely shifted to maximizing GPU utilization.</span></p> <h2><span style="font-weight: 400;">Why Latency Matters</span></h2> <p><span style="font-weight: 400;">To see why bounded and low-pMax latencies are important, let’s consider model training. During that training, hundreds of thousands of GPUs iterate over vast amounts of data in storage multiple times (i.e., over multiple epochs), and the GPUs train datasets in batches. Periodically, after every certain number of steps or batches, the GPUs synchronize their state among themselves. If one GPU is slow, this step will slow down all GPUs as well as the entire training. </span></p> <p><span style="font-weight: 400;">Figure 1 shows a data-loading pipeline across two GPUs. The dataloader in every GPU host prefetches the next dataset batch, while the GPU is processing the current batch for maximum compute or I/O overlap. In the case of GPU1, the storage-fetch latency is well within bounds, so the GPU is never stalled waiting on I/O. In the case of GPU2, there are two instances where storage fetch exhibits high latency, stalling GPU. As a result of these stalls, the overall step-completion time is delayed.</span></p> <figure id="attachment_24155" aria-describedby="caption-attachment-24155" style="width: 1469px" class="wp-caption alignnone"><img fetchpriority="high" decoding="async" class="size-full wp-image-24155" src="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png" alt="" width="1469" height="793" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png 1469w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png?resize=916,494 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png?resize=768,415 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png?resize=1024,553 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png?resize=96,52 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-1.png?resize=192,104 192w" sizes="(max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24155" class="wp-caption-text">Figure 1: Dataloading across two GPUs.</figcaption></figure> <h2><span style="font-weight: 400;">Legacy BLOB-Storage Architecture Wasn’t AI-Ready</span></h2> <p><span style="font-weight: 400;">Over the years, BLOB storage evolved organically, adding layers on top of layers in a true service-oriented fashion. Many of these layers were stateful and maintained their own metadata stores. While these metadata-access latencies typically weren’t the bottleneck for the traditional use cases served by global HDDs, they were showstoppers for AI workloads with millisecond access to data in flash. Figure 2 shows the request flow for a typical </span><span style="font-weight: 400; font-family: 'courier new', courier;">getObject(“/bucket/path”)</span><span style="font-weight: 400;"> API. After the request arrives at the API server, the server does many metadata lookups across the namelayer, volumeslayer, and containerlayer before resolving the path to a set of (blockId, offset, size) tuples. Some of these lookups can cross regions, and it&#8217;s not uncommon for latencies to add up to hundreds of milliseconds; one slow response from any of the lookups was sufficient. After the lookups, the API server proxies the data from the Tectonic layer to the client.</span></p> <figure id="attachment_24156" aria-describedby="caption-attachment-24156" style="width: 1321px" class="wp-caption alignnone"><img decoding="async" class="size-full wp-image-24156" src="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png" alt="" width="1321" height="884" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png 1321w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png?resize=916,613 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png?resize=768,514 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png?resize=1024,685 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png?resize=96,64 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-2.png?resize=192,128 192w" sizes="(max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24156" class="wp-caption-text">Figure 2: Old request flow for getObject API.</figcaption></figure> <p><span style="font-weight: 400;">While this architecture served conventional workloads well, the foundational assumptions that dictated design tradeoffs have since shifted. Some of these are:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Performance and latency: As discussed, while latency needs for conventional workloads were modest, AI workloads demand predictable and bounded latencies all the way up to pMax.<br /> </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Reliability and durability: The legacy architecture was designed to be highly durable and available, even in the face of region outages; data and metadata were globally replicated by default. While AI workloads demand very high availability, the global-by-default design choice no longer holds.<br /> </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Cost efficiency: Legacy stack was built on top of HDDs and highly optimized for cost per byte. The IOPS demands for AI workloads necessitate flash, and in addition, the computational cost of storage becomes negligible relative to the computational cost of GPUs.<br /> </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Power efficiency: With GPUs, datacenters are increasingly power constrained rather than space constrained. Every kilowatt of power spent on storage is power not spent on GPUs. This is a new constraint with AI workloads.</span></li> </ul> <p><span style="font-weight: 400;">In short, the tradeoff space has shifted enough for us to rethink the entire architecture.</span></p> <h2><span style="font-weight: 400;">Rebuilding the Foundation</span></h2> <p><span style="font-weight: 400;">As we set out to build the new foundation, we made the following major design choices:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Unified metadata schema: We rewrote the metadata subsystem and collapsed the metadata spread across different layers into one unified and flat schema backed by <a href="https://engineering.fb.com/2021/08/06/core-infra/zippydb/" target="_blank" rel="noopener">ZippyDB</a>. This paves the way for O(1) lookup to resolve paths to storage addresses, which is a step-function improvement.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">No dataplane proxy: We eliminated the dataplane proxy and built a fat client SDK that is capable of streaming bytes directly from storage servers to the clients. This helps with power-efficiency goals and also helps achieve higher throughput/lower latency.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Regional deployment: The BLOB-storage stack is now lean with flexibility to be deployed as a regional or global service. We now deploy a regional BLOB-storage stack colocated with GPUs in every AI region.</span></li> </ul> <figure id="attachment_24157" aria-describedby="caption-attachment-24157" style="width: 1294px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-24157" src="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png" alt="" width="1294" height="985" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png 1294w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png?resize=916,697 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png?resize=768,585 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png?resize=1024,779 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png?resize=96,73 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-3.png?resize=192,146 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24157" class="wp-caption-text">Figure 3: New request flow for getObject API.</figcaption></figure> <p><span style="font-weight: 400;">Figure 3 shows the new request flow for </span><span style="font-weight: 400; font-family: 'courier new', courier;">getObject(“/bucket/path”)</span><span style="font-weight: 400;">. When the SDK on the client receives this API call, it now issues a </span><span style="font-weight: 400; font-family: 'courier new', courier;">getReadPlan(“/bucket/path”)</span><span style="font-weight: 400;"> request to the API server. The API server does O(1) lookup per chunk to the new metadata store to map the path to (blockId, offset, size) tuples. It then returns the ReadPlanResult to the SDK. The SDK has Tectonic BlockClient embedded within it, and so is now able to stream data from these blocks directly from Tectonic. With these changes, we have rebuilt the foundations and met the goal of adding zero overhead on top of Tectonic. By eliminating the data proxy, we also stay within budget for the power footprint.</span></p> <h2><span style="font-weight: 400;">Dealing With Spikes and Hot Spots</span></h2> <p><span style="font-weight: 400;">During data and checkpoint loading, AI workloads are known to access data concurrently across hundreds of GPUs. Subsets of data such as model weights are often “hot,” and events such as GPU restarts trigger sharp traffic spikes. With the foundations now fixed, our next problem was dealing with those spikes and hot spots. Luckily, the BLOB-storage layer has had experience dealing with hot spots over the years, so we adapted existing solutions to AI workloads here. Specifically, we employed two approaches:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Distributed data cache: We leveraged the spare memory on the GPU hosts as a distributed data cache for frequently and concurrently accessed data. To achieve this, we reused components from </span><a href="https://engineering.fb.com/2022/07/14/data-infrastructure/owl-distributing-content-at-meta-scale/" target="_blank" rel="noopener"><span style="font-weight: 400;">Meta’s Owl subsystem</span></a><span style="font-weight: 400;">: We integrated the peers in the Owl subsystem directly into the BLOB-storage client SDK so that all data access goes through this data cache. </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Readplan metadata cache: Readplan refers to the mapping from path to storage address. We now cache the read-plan for frequently accessed BLOBs in a distributed-memory store similar to memcache. </span></li> </ul> <p><span style="font-weight: 400;">In practice we observe an average cache hit rate of 80% on the distributed data cache, and the read-plan cache provides 1-2 ms access to metadata. In essence, these simple mechanisms do three things: </span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Absorb the spikes and reduce the I/O requirements from storage.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Solve the problem of metadata hot shards.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Improve p50 and p99 latencies by serving from memory.</span></li> </ul> <h2><span style="font-weight: 400;">Protocol Optimizations</span></h2> <p><span style="font-weight: 400;">What we’ve discussed so far got us 80% of the way. We achieved the remaining 20% by identifying and fixing bottlenecks across the stack. Below are some noteworthy problems, though not an exhaustive list by any means:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Laggards: One slow storage node contributing to tail latencies. This is a well-understood problem, and we resorted to hedged reads on the client side to mitigate this. </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Egress spikes: During checkpoint events, it is common for the client to create sharp egress spikes. This in turn can cause congestion, timeouts, and retries, eventually stalling GPUs. We resolved this by building dynamic concurrency control on the client SDK to automatically tune parallelism based on application-level congestion signals. </span></li> </ul> <p><span style="font-weight: 400;">With all of the above, the new BLOB-storage stack is now capable of serving AI workloads without causing GPU stalls, adding negligible overhead on top of the Tectonic layer. Our next focus shifted to research.</span></p> <h2><span style="font-weight: 400;">Maximizing Research Velocity</span></h2> <p><span style="font-weight: 400;">GPUs are scarce and increasingly becoming geo-distributed; at the same time, training workloads need data colocated with GPUs for performance reasons. This creates an interesting challenge for researchers: They are now on the hook for ingesting and moving datasets across regions. </span></p> <p><span style="font-weight: 400;">At Meta, a typical training-job submission involves the following:</span></p> <ol> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A researcher curates data from various sources, enriches them and persists them in BLOB storage.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The researcher picks a region where they want to run the job.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The researcher submits a data-ingestion job, which creates a snapshot of the training datasets onto the target region in a file format optimized for data loading from within the GPU host.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The researcher then waits for ingestion to finish; depending on the dataset size, that can take hours. </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The researcher submits their training job and monitors their run.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The researcher analyzes outputs, tweaks datasets, and iterates again, starting with Step 3.</span></li> </ol> <p><span style="font-weight: 400;">Steps 2 through 4 can take hours and directly impact the speed of iteration for researchers. Ideally, we like our researchers’ time to be spent on tuning models, not waiting for storage. Currently, researchers copy snapshots before starting their jobs to colocate data with GPUs, which results in the most optimal performance. While this optimization for performance makes sense for large-scale training jobs that span weeks or months, the vast majority of jobs are much smaller; the researchers owning these jobs are more than willing to trade off occasional performance degradation for iteration speed.</span></p> <p><span style="font-weight: 400;">And so, we needed a system where researchers are able to ingest data once and access data anywhere without thinking about regional boundaries. We needed a workflow that allows researchers to iterate in minutes and not hours. As we went back to the drawing board, the write-once, read-many characteristic of these datasets rang a bell. What if we think of storage as a disk in a planet-scale computer and borrow ideas from the operating-system world? When a Linux process running on a CPU core attempts to read a file from disk, the operating system transparently hydrates data on demand across the various layers of the cache—page cache in memory and L2 and L1 CPU caches. This intuition led to the architectural evolution in Figure 4:</span></p> <figure id="attachment_24158" aria-describedby="caption-attachment-24158" style="width: 1999px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-24158" src="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png" alt="" width="1999" height="1340" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png 1999w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=916,614 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=768,515 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=1024,686 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=1536,1030 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=96,64 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-4.png?resize=192,129 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24158" class="wp-caption-text">Figure 4: Dataloading architecture evolution.</figcaption></figure> <p><span style="font-weight: 400;">The core idea is to leverage the various on-host and off-host storage resources as a tiered cache with global BLOB-storage fabric backed by HDDs as the ultimate source of truth. Specifically, we leverage the memory and flash on the GPU host as L1 and L2 caches. And we leverage the regional BLOB-storage fabric backed by flash as the L3 cache dataloader continues to access storage through the familiar BLOB-storage SDK. To effectively hide latencies and to simplify the data life cycle, we rely on the following:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Dataloader prefetch: Dataloaders prefetch the next batch of datasets into memory while processing the current batch. This prefetch will surface as a </span><i><span style="font-weight: 400;">read </span></i><span style="font-weight: 400;">operation at the BLOB-storage SDK level.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Deep prefetch: We expose an explicit prefetch() API as part of the BLOB-storage SDK. The dataloader will trigger explicit prefetch of the data needed during the next few minutes by invoking the prefetch() API in the background. This API triggers hydration of data from remote storage onto the local region L3 cache and also prewarms the metadata cache.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Automatic data life cycle: Data in the L3 regional disaggregated flash tier is typically held for a configured period of time to allow reuse across epochs in a training cycle. We support custom eviction policies, including TTL and LRU policies. The eviction policies are also capacity/quota aware.</span></li> </ul> <p><span style="font-weight: 400;">We saw rapid adoption of this new data-loading paradigm as soon as production rollout started, and we continue to support both of the data-loading paradigms in production today. To illustrate the impact in numbers, Figure 5 shows roughly the ingestion times before and after the rollout across all workloads:</span></p> <figure id="attachment_24154" aria-describedby="caption-attachment-24154" style="width: 600px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="wp-image-24154" src="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png" alt="" width="600" height="248" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png 1258w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png?resize=916,379 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png?resize=768,317 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png?resize=1024,423 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png?resize=96,40 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Meta-AI-Storage-Blueprint-image-5-1-e1782843150694.png?resize=192,79 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24154" class="wp-caption-text">Figure 5: Ingestion times before and after the rollout.</figcaption></figure> <p><span style="font-weight: 400;">In a world where new frontier models get released in weeks, this shift in the data-loading paradigm is a much-needed change to move even faster.</span></p> <h2><span style="font-weight: 400;">Key Takeaways</span></h2> <p><span style="font-weight: 400;">Modern AI workloads are data hungry, and storage plays an important role in both the computational cost and speed of innovation. Storage bottlenecks directly impact GPU utilization and computational cost, and in a world with geo-distributed GPUs, time spent on cross-region data ingestion directly impacts the speed of iteration in research. The BLOB-storage architecture at Meta was built to serve Meta’s family of apps, and we needed a step-function improvement in performance to serve AI workloads. This led to rethinking the entire architecture. By rebuilding the metadata subsystem and by adopting a tiered caching architecture with prefetching/on-demand hydration, we are able to meet the needs of today’s workloads effectively.</span></p> <h2><span style="font-weight: 400;">Future Work</span></h2> <p><span style="font-weight: 400;">We are continuously evolving storage at Meta to keep up with hardware evolution and workload demands. Some future work in this area will include: </span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Scaling storage to network limits.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Supporting checkpointing without stalling GPUs at even higher scale.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">New challenges for inference workloads, which we are starting to tackle.</span></li> </ul> <p>The post <a href="https://engineering.fb.com/2026/07/01/data-infrastructure/metas-ai-storage-blueprint-at-scale/">Meta&#8217;s AI Storage Blueprint at Scale</a> appeared first on <a href="https://engineering.fb.com">Engineering at Meta</a>.</p> 6 security settings every GitHub maintainer should enable this week - The GitHub Blog https://github.blog/?p=97204 2026-07-01T15:59:29.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">At GitHub Security Lab, we spend a lot of our week talking to maintainers. Some find the settings page dense and the docs sprawl. Most maintainers we talk to weren&rsquo;t hired to be security engineers. While this is true, ignoring a project&rsquo;s security settings completely will lead into leaving a lot in the table in terms of automation and scalability, leading into a poor security posture, and before you realize it to vulnerabilities that pile up, exposing your users.</p> <p class="wp-block-paragraph">Here&rsquo;s the short version. Six settings, free to use, updated in less than half an hour. We&rsquo;ve bundled them into a guided flow called <a href="https://securitylab.github.com/protect-your-project.html">Protect Your Project</a> so you can do them in one pass, and we walk through each tool you&rsquo;ll use below.</p> <h2 id="h-1-add-a-security-md-file" class="wp-block-heading">1. Add a SECURITY.md file</h2> <p class="wp-block-paragraph">This is the lightest-lift setting on the list and the one that makes everything else easier.</p> <p class="wp-block-paragraph">A <code>SECURITY.md</code> file tells the people who find bugs in your project where to send them. Without one, your options for a well-meaning reporter are a public issue (now a public exploit) or your personal email (if they can find it).</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="374" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.09-PM.png?resize=1024%2C374" alt="Screenshot of GitHub settings. Security and quality &gt; Set up a security policy are highlighted." class="wp-image-97205" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.09-PM.png?w=1306 1306w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.09-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.09-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.09-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">You don&rsquo;t need to write much. We suggest adding a communication mean such as an email so that those reporting vulnerabilities can reach you directly without posting about them publicly. Then, you can state what bugs are in scope, alongside anything else a reporter should have in mind when contacting you. For reference, we point maintainers to the the <a href="https://github.com/systemd/systemd/security/policy">systemd project&rsquo;s security policy</a> that we consider a complete example. It sets clear expectations about reproducers and doesn&rsquo;t assume you have a 24/7 response team when you don&rsquo;t. Borrow the structure, change the contact details, commit it.</p> <p class="wp-block-paragraph">Ten minutes, tops.</p> <h2 id="h-2-turn-on-private-vulnerability-reporting" class="wp-block-heading">2. Turn on private vulnerability reporting</h2> <p class="wp-block-paragraph"><code>SECURITY.md</code> tells reporters where to go. Private vulnerability reporting (PVR) gives them a private place to make their report.</p> <p class="wp-block-paragraph">Once enabled, a researcher can file a confidential advisory on your repo. You triage it out of the public eye and disclose on your timeline. The setup is one checkbox in Settings &rarr; Security.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="371" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.19-PM.png?resize=1024%2C371" alt="Screenshot of GitHub settings. Security and quality &gt; Enable vulnerability reporting is highlighted." class="wp-image-97206" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.19-PM.png?w=1308 1308w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.19-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.19-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.19-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">If you only do one thing tonight, do these first two together. They are free, and are the fastest signal to your community that you take this seriously.</p> <h2 id="h-3-turn-on-secret-scanning-with-push-protection" class="wp-block-heading">3. Turn on secret scanning, with push protection</h2> <p class="wp-block-paragraph">This is the one with the most embarrassing failure mode.</p> <p class="wp-block-paragraph">GitGuardian&rsquo;s <a href="https://www.gitguardian.com/state-of-secrets-sprawl-report-2026">State of Secrets Sprawl 2026</a> found 28.65 million new secrets leaked on public GitHub in 2025, a 34% jump over the prior year and the largest single-year increase on record. AI-assisted commits are leaking secrets at roughly twice the baseline rate. The average cost of a data breach now sits at $4.44 million globally ($10.22 million in the US) per <a href="https://www.bluefin.com/bluefin-news/ibms-2025-data-breach-report-key-findings-and-the-years-biggest-attacks/">IBM&rsquo;s 2025 Cost of a Data Breach Report</a>.</p> <p class="wp-block-paragraph">Secret scanning catches keys and tokens that slip into your repo by blocking them locally before they&rsquo;re pushed to your repository. It doesn&rsquo;t matter if your repo is public or private, because once secrets leave your local development, then they are available to anyone with access to your repo.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="405" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.27-PM.png?resize=1024%2C405" alt="Screenshot of GitHub settings. Security and quality &gt; View detected secrets is highlighted." class="wp-image-97207" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.27-PM.png?w=1306 1306w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.27-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.27-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.27-PM.png?w=1024 1024w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <h2 id="h-4-turn-on-dependabot-and-dependency-review" class="wp-block-heading">4. Turn on Dependabot and dependency review</h2> <p class="wp-block-paragraph">Your project isn&rsquo;t just your code. It&rsquo;s the dozens (often hundreds) of packages your code pulls in.</p> <p class="wp-block-paragraph">Looking at WordPress, for example: <a href="https://github.com/advisories?query=Wordpress+type%3Areviewed+severity%3Acritical">this search for reviewed, critical-severity advisories mentioning WordPress</a> returns a long list of plugins with known vulnerabilities. If you&rsquo;re running a WordPress site, Dependabot helps ensure none of these plugins are sitting in your dependencies.</p> <p class="wp-block-paragraph">Dependabot alerts you when a package you depend on has a known vulnerability. Dependency review shows you, inside a pull request, exactly what&rsquo;s being added or upgraded and whether any of it has an open advisory. Together they turn an opaque <code>package.json</code> diff into a two-minute review.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="403" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.38-PM.png?resize=1024%2C403" alt="Screenshot of GitHub settings. Security and quality &gt; View Dependabot alerts is highlighted." class="wp-image-97208" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.38-PM.png?w=1307 1307w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.38-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.38-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.38-PM.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure> <h2 id="h-5-turn-on-code-scanning" class="wp-block-heading">5. Turn on code scanning</h2> <p class="wp-block-paragraph">Code scanning runs static analysis on your repo and flags the patterns that lead to real bugs. SQL injection. Command injection. Dangerous deserialization. The usual cast.</p> <p class="wp-block-paragraph">Code scanning with CodeQL can detect unsafe GitHub Actions workflows. CodeQL is the engine, and we built code scanning. We made it free for open source in 2019, and it now ships as a one-click default setup in your Security and Quality tab.</p> <p class="wp-block-paragraph">This is the setting most maintainers skip because it sounds like it needs configuration. It doesn&rsquo;t. Default setup picks the right query pack for your language and runs on every pull request.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="403" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.46-PM.png?resize=1024%2C403" alt="Screenshot of GitHub settings. Security and quality &gt; Set up code scanning is highlighted." class="wp-image-97209" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.46-PM.png?w=1306 1306w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.46-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.46-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.28.46-PM.png?w=1024 1024w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure> <h2 id="h-6-turn-on-branch-protection-on-your-default-branch" class="wp-block-heading">6. Turn on branch protection on your default branch</h2> <p class="wp-block-paragraph">This is the simplest, least-flashy setting, but it will yield the biggest impact starting as soon as you turn it on. This is about requiring a pull request before merging to <code>main</code> with minimum one approval.</p> <figure class="wp-block-video"><video height="1660" style="aspect-ratio: 2832 / 1660;" width="2832" controls poster="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-30-at-3.33.54-PM.png" src="https://github.blog/wp-content/uploads/2026/06/branch-pro.mp4"></video></figure> <p class="wp-block-paragraph">This catches the worst-case scenario: a compromised credential, a confused contributor, or a tired version of you pushing straight to production. It&rsquo;s also what makes the other five settings actually bite, because now Dependabot alerts and code scanning findings block a merge instead of sitting in a tab you never open.</p> <aside data-color-mode="light" data-dark-theme="dark" data-light-theme="light_dimmed" class="wp-block-group post-aside--large p-4 p-md-6 is-style-light-dimmed has-global-padding is-layout-constrained wp-block-group-is-layout-constrained is-style-light-dimmed--1" style="border-top-width:4px"> <h2 id="h-the-protect-your-project-shortcut" class="wp-block-heading h5-mktg gh-aside-title is-typography-preset-h5" style="margin-top:0">The Protect Your Project shortcut</h2> <p class="wp-block-paragraph">We built <a href="https://securitylab.github.com/protect-your-project.html">Protect Your Project</a> so you don&rsquo;t have to remember any of this. It&rsquo;s a guided wizard that walks you through these six settings on one repo in 10 to 15 minutes, without signing up.</p> <p class="wp-block-paragraph">Now that you understand what each setting does, you can use this tool to turn them on.</p> <p class="wp-block-paragraph"><a href="https://securitylab.github.com/protect-your-project.html">Get started &gt;</a></p> </aside> <h2 id="h-in-conclusion" class="wp-block-heading">In conclusion</h2> <p class="wp-block-paragraph">These six settings will not make your project unhackable. Nothing will.</p> <p class="wp-block-paragraph">What they will do is close the easy doors, the ones being walked through right now by people scripting through public repos at scale.</p> <p class="wp-block-paragraph">Turn these on, and your project will be meaningfully harder to attack than it was this morning. So will every project that depends on it.</p> </body></html> <p>The post <a href="https://github.blog/security/6-security-settings-every-github-maintainer-should-enable-this-week/">6 security settings every GitHub maintainer should enable this week</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> How GitHub maintains compliance for open source dependencies - The GitHub Blog https://github.blog/?p=97187 2026-06-30T17:28:16.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Every day, GitHub engineers introduce new dependencies into the GitHub platform, internal applications, and open source projects. GitHub is not just the home of open source; it is powered by open source! And an important part of using open source responsibly is respecting the licenses that govern the projects you depend on.</p> <p class="wp-block-paragraph">At GitHub, we are committed to upholding our obligations to the open source community and to the dependencies we use. Here&rsquo;s how our Open Source Program Office (OSPO) uses the new GitHub License Compliance feature to manage thousands of dependencies.</p> <h2 id="h-managing-the-open-source-license-compliance-process" class="wp-block-heading">Managing the open source license compliance process</h2> <p class="wp-block-paragraph">Nearly all software carries some kind of license agreement. The license gives you permission to use a project, provided you comply with its obligations. Those obligations may be as simple as giving credit to the original author in your documentation, or they may require you to distribute all your source code when shipping your program. In some cases, licenses may also restrict certain activities or categories of use.</p> <p class="wp-block-paragraph">Your organization likely has its own policies about acceptable licenses based on your business model, software ecosystem, and distribution strategy. For example, suppose your organization sells a commercial, closed source binary application. You may want to prevent dependencies that would require you to open source your proprietary code.</p> <p class="wp-block-paragraph">Or, you may have a project that you plan to release as an open source package. In this case, you may want to avoid including dependencies governed by commercial or incompatible open source licenses.</p> <p class="wp-block-paragraph">If you can&rsquo;t comply with the obligations required in either scenario, you should avoid the dependency to prevent legal or operational risks. It may require engineering effort to remove these licenses after the fact. For enterprise software, the business risk of noncompliance is huge because it can lead to costly litigation and reputational damage.</p> <p class="wp-block-paragraph">Traditionally, license reviews have been performed manually or with third-party software. But now, GitHub has introduced a license compliance feature for GitHub Advanced Security customers, enabling you to review new dependencies directly on pull requests. This review helps ensure that the licenses for those dependencies&rsquo; comply with your policy, while also giving you the flexibility to expand your policy to allow new licenses or individual projects.</p> <p class="wp-block-paragraph">Two months ago, GitHub&rsquo;s OSPO migrated from internal-only tools that we&rsquo;d built to manage compliance onto the new feature. As early adopters, we gave the development team quick feedback and helped ensure the feature would clear the bar for large, fast-moving enterprises with complex compliance requirements.</p> <h2 id="h-setting-up-for-policy-success" class="wp-block-heading">Setting up for policy success</h2> <p class="wp-block-paragraph">Because GitHub had built internal license compliance tools prior to the introduction of the product, we had an existing list of acceptable licenses to use as our initial policy. You&rsquo;ll likely find that many dependencies use common permissive licenses such as MIT, Apache 2.0, and BSD-3-Clause, which are a good starting list to seed your policy. We initially rolled the feature out using the &ldquo;Evaluate&rdquo; mode on an organization-wide ruleset, which generated annotations in pull requests without blocking merges, so we were able to get developers accustomed to the new workflow without impeding their productivity. Running the old and new tools in parallel also let us see if their behavior diverged. After about a month of this mode of operation, we got to a state where the alerts were mainly on packages with unusual, missing, or explicitly disallowed licenses.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="936" height="504" src="https://github.blog/wp-content/uploads/2026/06/613757407-388b97bc-ca84-416a-aac5-72e4924e5dc8.png?resize=936%2C504" alt="The enterprise license policy screen has a paginated list of SPDX licenses, with the ability to add more via manual input or a selection dialog." class="wp-image-97190" srcset="https://github.blog/wp-content/uploads/2026/06/613757407-388b97bc-ca84-416a-aac5-72e4924e5dc8.png?w=936 936w, https://github.blog/wp-content/uploads/2026/06/613757407-388b97bc-ca84-416a-aac5-72e4924e5dc8.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/613757407-388b97bc-ca84-416a-aac5-72e4924e5dc8.png?w=768 768w" sizes="(max-width: 936px) 100vw, 936px" /></figure> <h2 id="h-how-github-license-compliance-works" class="wp-block-heading">How GitHub license compliance works</h2> <p class="wp-block-paragraph">Under the hood, license compliance checks are enabled via rulesets. We target repositories via a custom property, where the value of the property determines whether license checks are enabled in &ldquo;Active&rdquo; or &ldquo;Evaluate&rdquo; mode. In repositories that are targeted by a ruleset, pull requests that modify a project&rsquo;s dependencies trigger a scan that looks up the licenses used by each of the new dependencies. If the new dependencies&rsquo; licenses are already permitted, or there are package-specific exceptions, the checks pass. If there are failures, either in the direct or transitive dependencies, the tool comments on the pull request with alerts for each problematic package.</p> <figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="936" height="598" src="https://github.blog/wp-content/uploads/2026/06/613757492-a055f40b-3546-4726-8ff8-fa28f2729d44.png?resize=936%2C598" alt="A license alert page, with the name of the package and the noncompliant license identifier and a timeline showing communications between the developer and approver. " class="wp-image-97191" srcset="https://github.blog/wp-content/uploads/2026/06/613757492-a055f40b-3546-4726-8ff8-fa28f2729d44.png?w=936 936w, https://github.blog/wp-content/uploads/2026/06/613757492-a055f40b-3546-4726-8ff8-fa28f2729d44.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/613757492-a055f40b-3546-4726-8ff8-fa28f2729d44.png?w=768 768w" sizes="(max-width: 936px) 100vw, 936px" /></figure> <p class="wp-block-paragraph">The developer then reviews the alerts. If they decide the dependency is unacceptable, they can update their code or close the pull request to remove it. If they believe the license or package should be allowed, they can raise an exception request which will notify a specific team in the organization who can decide whether and how to amend the policy.</p> <h2 id="h-a-day-in-the-life-of-the-license-policy-team" class="wp-block-heading">A day in the life of the license policy team</h2> <p class="wp-block-paragraph">GitHub&rsquo;s license policy team consists of OSPO members and engineers with expertise in license reviews and supply chain analysis. Since we are a worldwide company, our policy review team has members across time zones to review alerts in a timely manner. We are in the process of formalizing an SLA for reviewing license requests, but in practice it&rsquo;s rarely more than a couple of hours before we can triage an incoming request.</p> <p class="wp-block-paragraph">Team members receive email notifications of new review requests and can also access a dashboard to see the backlog of pending requests.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="1024" width="936" src="https://github.blog/wp-content/uploads/2026/06/613757588-b5723bcc-5867-419b-983b-6c997ee8a31e.png?resize=936%2C1024" alt="An email notification sent to the license reviewer team, with the name of the user who raised the alert, the repository where the alert was generated, and a comment from the developer requesting the package be permitted because it is a private, internal package." class="wp-image-97192" srcset="https://github.blog/wp-content/uploads/2026/06/613757588-b5723bcc-5867-419b-983b-6c997ee8a31e.png?w=938 938w, https://github.blog/wp-content/uploads/2026/06/613757588-b5723bcc-5867-419b-983b-6c997ee8a31e.png?w=274 274w, https://github.blog/wp-content/uploads/2026/06/613757588-b5723bcc-5867-419b-983b-6c997ee8a31e.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/613757588-b5723bcc-5867-419b-983b-6c997ee8a31e.png?w=936 936w" sizes="(max-width: 936px) 100vw, 936px" /></figure> <p class="wp-block-paragraph">When approving a request, we have two decision points: first, whether to permit the license or the package. Then, decide what scope &ndash; enterprise or repository &ndash; to use. If it&rsquo;s a safe license that simply hasn&rsquo;t shown up before, we&rsquo;ll add it at the enterprise level and thus allow dependencies with that license anywhere at GitHub. Some packages carry a commercial license which can&rsquo;t be permitted everywhere but should be allowed in the repository owned by a team which has paid for the software, so those policy amendments get added at the repository level. Package exceptions are useful for internal software which usually doesn&rsquo;t have license data associated with it. Helpfully, the tool supports wildcard matches for package exceptions. For example, we&rsquo;ve permitted everything in the <code>@github-ui/*</code> React namespace, so we don&rsquo;t need to approve those packages one by one.</p> <h2 id="h-making-it-easy-for-developers" class="wp-block-heading">Making it easy for developers</h2> <p class="wp-block-paragraph">To support this process, we&rsquo;ve established procedures about contacting the GitHub OSPO, and how to use an emergency &ldquo;break glass&rdquo; override. These situations should be rare, but a clear emergency override process is essential for critically time-sensitive pull requests. As we mentioned above, the license policy enforcement happens via ruleset, and the ruleset condition keys off a custom property. So toggling the value of the property can temporarily turn off enforcement if there&rsquo;s a critical fix that&rsquo;s blocked by a license alert. So far, we&rsquo;ve only needed to use this once, but it was very helpful to have the option.</p> <p class="wp-block-paragraph">We&rsquo;ve also provided internal documentation and training to help developers understand the importance of license compliance. Ultimately, it&rsquo;s everyone&rsquo;s job to help ensure compliance and manage risk and it&rsquo;s our job to make that as easy as possible.</p> <h2 id="h-wrapping-up" class="wp-block-heading">Wrapping up</h2> <p class="wp-block-paragraph">License compliance is a critical part of managing our software supply chain. By helping developers make informed dependency choices aligned with GitHub&rsquo;s license policy we prevent costly rewrites and potential legal problems. We&rsquo;ve been enthusiastically using and providing feedback on the new GitHub License Compliance feature for several months. Now that it&rsquo;s in public preview, we are excited to see more companies adopt it and hope our experience provides some guidance if you&rsquo;re just getting started.</p> <p class="wp-block-paragraph">GitHub Enterprise Cloud customers can use the License Compliance feature across repositories which have an active GHAS Code Security license. For more information, see <a href="https://docs.github.com/enterprise-cloud@latest/code-security/concepts/supply-chain-security/open-source-license-compliance">About open source license compliance</a>.</p> </body></html> <p>The post <a href="https://github.blog/enterprise-software/governance-and-compliance/how-github-maintains-compliance-for-open-source-dependencies/">How GitHub maintains compliance for open source dependencies</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> 10 Years of Meta’s Commitment to Python - Engineering at Meta https://engineering.fb.com/?p=24128 2026-06-30T16:00:46.000Z <p><span style="font-weight: 400;">This year marks Meta&#8217;s 10th consecutive year as a sponsor of the </span><a href="https://www.python.org/psf-landing/" target="_blank" rel="noopener"><span style="font-weight: 400;">Python Software Foundation (PSF)</span></a><span style="font-weight: 400;">, </span><span style="font-weight: 400;">the charitable organization dedicated to advancing, supporting, and protecting the open-source Python programming language and the community that sustains it. </span><span style="font-weight: 400;">Python is one of the world&#8217;s most influential programming languages, and we use it across our engineering stack, from the</span> <a href="https://engineering.fb.com/2023/09/07/culture/threads-inside-story-metas-newest-social-app/" target="_blank" rel="noopener"><span style="font-weight: 400;">backend of our apps and products like Instagram and Threads</span></a><span style="font-weight: 400;"> to </span><a href="https://ai.meta.com/research/publications/neuralset-a-high-performing-python-package-for-neuro-ai/" target="_blank" rel="noopener"><span style="font-weight: 400;">cutting-edge AI research</span></a><span style="font-weight: 400;">. </span></p> <p><span style="font-weight: 400;">We recognize the vital role the PSF plays in sustaining the language, nurturing its global community, and driving innovation. After a decade, it felt like the right moment to reflect on why we, as an organization of engineers, are committed to funding the PSF. </span><span style="font-weight: 400;">By supporting the PSF, we aim to help ensure that Python remains robust, innovative and accessible for generations of engineers to come. We hope our involvement will inspire other individuals and organizations to join us in strengthening the foundation that supports so much of today’s technology.</span></p> <h1><span style="font-weight: 400;">The Importance of Python at Meta</span></h1> <p><span style="font-weight: 400;">Python is the most used programming language at Meta. It powers infrastructure across our most important products and initiatives and supports a wide range of teams across the company. Some of the core maintainers of Python are Meta engineers who have authored </span><a href="https://engineering.fb.com/2023/10/05/developer-tools/python-312-meta-new-features/" target="_blank" rel="noopener"><span style="font-weight: 400;">new features and Python Enhancement Proposals (PEPs)</span></a><span style="font-weight: 400;"> for the Python community. </span><a href="https://pytorch.org/"><span style="font-weight: 400;">PyTorch</span></a><span style="font-weight: 400;">, one of the world’s most widely-used machine learning frameworks, was </span><a href="https://ai.meta.com/blog/pytorch-builds-the-future-of-ai-and-machine-learning-at-facebook/" target="_blank" rel="noopener"><span style="font-weight: 400;">originally developed at Meta in partnership with the community</span></a><span style="font-weight: 400;"> before being spun off into </span><a href="https://ai.meta.com/blog/pytorch-foundation/" target="_blank" rel="noopener"><span style="font-weight: 400;">its own independent foundation</span></a><span style="font-weight: 400;">. Meta also builds open-source Python developer tools to help developers write better quality, more performant Python. This includes projects like</span> <a href="https://pyrefly.org/"><span style="font-weight: 400;">Pyrefly</span></a><span style="font-weight: 400;">, an incredibly fast type checker and language server.</span></p> <p><span style="font-weight: 400;">Supporting the continued growth and sustainability of Python is a natural fit for Meta’s technical vision. It will continue to play an important role in helping us achieve our goals as we invest further in AI, build new data-driven products, and further scale our infrastructure.</span></p> <h1><span style="font-weight: 400;">Why Meta Sponsors the Python Software Foundation</span></h1> <p><span style="font-weight: 400;">At Meta we understand that using open source software like Python comes with a shared responsibility to help ensure the language and its ecosystem remain healthy, secure, and innovative for everyone. Every product shipped, every model trained, and every insight generated with Python is made possible by the collective work of the open source community, backed up by the organizational support and infrastructure maintained by the PSF. For Meta, supporting the PSF is a strategic investment in the future of Python, and hence the long-term stability of our own technology stack. </span></p> <p><span style="font-weight: 400;">Our sponsorship of the PSF has helped fund impactful initiatives such as the </span><a href="https://www.python.org/psf/developersinresidence/" target="_blank" rel="noopener"><span style="font-weight: 400;">Developer-in-Residence program</span></a><span style="font-weight: 400;">, which employs full-time developers who are focused on improving the Python programming language and its ecosystem. This program has been transformative, allowing critical work to happen that would otherwise fall to overstretched volunteers or go unaddressed entirely.</span></p> <p><span style="font-weight: 400;">PSF funding also goes towards strengthening the core infrastructure of the Python ecosystem, most notably the </span><a href="https://pypi.org/" target="_blank" rel="noopener"><span style="font-weight: 400;">Python Package Index (PyPI)</span></a><span style="font-weight: 400;">, where our sponsorship has helped fund essential security enhancements. These improvements are vital for protecting the global Python community and ensuring that developers everywhere – including our own engineers – can safely share and consume packages.</span></p> <p><span style="font-weight: 400;">Beyond purely technical investment, Meta’s support also helps fund educational programs and community events like </span><a href="https://us.pycon.org/2026/" target="_blank" rel="noopener"><span style="font-weight: 400;">PyCon US</span></a><span style="font-weight: 400;">, where we’ve provided free and discounted passes to PyCon, supported workshops and summits, and contributed to fundraising efforts for groups like </span><a href="https://pyladies.com/" target="_blank" rel="noopener"><span style="font-weight: 400;">PyLadies</span></a><span style="font-weight: 400;">. These investments help grow the Python community and foster the new talent that is essential for Python’s long-term sustainability.</span></p> <p><span style="font-weight: 400;">In short, sponsorship of the PSF is a valuable investment in the tools and community that make our work possible.</span></p> <h1><span style="font-weight: 400;">How Can You Support the Python Software Foundation?</span></h1> <p><span style="font-weight: 400;">There are several ways you as an individual, or your organization as a whole, can contribute to the ongoing success and sustainability of the PSF:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><a href="https://donate.python.org?ref=engineeringatmeta" target="_blank" rel="noopener"><span style="font-weight: 400;"><strong>Make a one time donation</strong></span></a><span style="font-weight: 400;">: You can give any amount as a one off donation.</span></li> <li style="font-weight: 400;" aria-level="1"><a href="https://www.python.org/psf/membership?ref=engineeringatmet" target="_blank" rel="noopener"><span style="font-weight: 400;"><strong>Become a PSF member</strong></span></a><span style="font-weight: 400;">: By becoming a member you can vote in discussions on the direction of the Python language. There are different donation tiers available, including donating your time.</span></li> <li style="font-weight: 400;" aria-level="1"><a href="https://www.python.org/sponsors/application?ref=engineeringatmet" target="_blank" rel="noopener"><span style="font-weight: 400;"><strong>Become a sponsor</strong></span></a><span style="font-weight: 400;">: For organizations looking to make a sustained impact, the PSF offers annual sponsorship tiers, each with increasing levels of recognition and benefits.</span></li> </ul> <p><span style="font-weight: 400;">As an organization the most meaningful way for you to support the PSF is through annual sponsorship. Besides benefitting from the continued success of the Python language itself, there are a range of additional benefits depending on your sponsorship amount. Sponsors of the PSF receive public recognition, with their names and logos featured on the </span><a href="https://www.python.org/psf-landing/"><span style="font-weight: 400;">PSF website</span></a><span style="font-weight: 400;">, in annual reports, and at major events. Sponsorship also provides valuable opportunities for community engagement, allowing organizations more opportunities to connect with the global Python community, participate in events, and demonstrate their commitment to open source. Higher-tier sponsors benefit from increased brand visibility through prominent logo placement and may be invited to speak or participate in special initiatives.</span></p> <h1><span style="font-weight: 400;">Thank You!</span></h1> <p><span style="font-weight: 400;">Finally, we want to say thank you to the Python community: the maintainers, contributors, educators, and advocates who make Python what it is today. Your passion and dedication are the foundation of Python’s success, and we’re proud to be able to support you, both as collaborators and sponsors.</span></p> <p><span style="font-weight: 400;">Visit our website to learn more about </span><a href="https://opensource.fb.com?ref=engineeringatmeta"><span style="font-weight: 400;">Meta Open Source</span></a><span style="font-weight: 400;">. You can also subscribe to our </span><a href="https://www.youtube.com/channel/UCCQY962PmHabTjaHv2wJzfQ" target="_blank" rel="noopener"><span style="font-weight: 400;">YouTube channel</span></a><span style="font-weight: 400;">, or follow us on </span><a href="https://www.facebook.com/MetaOpenSource" target="_blank" rel="noopener"><span style="font-weight: 400;">Facebook</span></a><span style="font-weight: 400;">, </span><a href="https://www.threads.net/@metaopensource" target="_blank" rel="noopener"><span style="font-weight: 400;">Threads</span></a><span style="font-weight: 400;">, </span><a href="https://bsky.app/profile/metaopensource.bsky.social"><span style="font-weight: 400;">Bluesky</span></a><span style="font-weight: 400;">, </span><a href="https://www.linkedin.com/showcase/meta-open-source?fbclid=IwZXh0bgNhZW0CMTEAAR2fEOJNb7zOi8rJeRvQry5sRxARpdL3OpS4sYLdC1_npkEy60gBS1ynXwQ_aem_mJUK6jEUApFTW75Emhtpqw"><span style="font-weight: 400;">LinkedIn</span></a><span style="font-weight: 400;">, and </span><a href="https://x.com/MetaOpenSource" target="_blank" rel="noopener"><span style="font-weight: 400;">X</span></a><span style="font-weight: 400;">.</span></p> <p>The post <a href="https://engineering.fb.com/2026/06/30/open-source/10-years-of-metas-commitment-to-python/">10 Years of Meta’s Commitment to Python</a> appeared first on <a href="https://engineering.fb.com">Engineering at Meta</a>.</p> Highlights from Git 2.55 - The GitHub Blog https://github.blog/?p=97076 2026-06-29T17:25:25.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">The open source Git project just released Git 2.55 with features and bug fixes from over 100 contributors, 33 of them new. We last caught up with you on the latest in Git <a href="https://github.blog/open-source/git/highlights-from-git-2-54/">back when 2.54 was released</a>.</p> <p class="wp-block-paragraph">To celebrate this most recent release, here is GitHub&rsquo;s look at some of the most interesting features and changes introduced since last time.</p> <h2 id="h-repacking-with-incremental-multi-pack-indexes" class="wp-block-heading">Repacking with incremental multi-pack indexes</h2> <p class="wp-block-paragraph">Returning readers of this series may recall our coverage of <a href="https://github.blog/open-source/git/highlights-from-git-2-47/#incremental-multi-pack-indexes">incremental multi-pack indexes</a> and <a href="https://github.blog/open-source/git/highlights-from-git-2-50/#h-incremental-multi-pack-reachability-bitmaps">incremental multi-pack reachability bitmaps</a>. In case you could use a refresher, here&rsquo;s the short version.</p> <p class="wp-block-paragraph">Git stores the contents of your repository as individual <a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects">objects</a>: commits, trees, and blobs. Those objects usually live in <a href="https://git-scm.com/book/en/v2/Git-Internals-Packfiles">packfiles</a>, which are compressed collections of objects. A packfile has a corresponding <a href="https://git-scm.com/docs/gitformat-pack">pack index</a> that lets Git locate any object inside the pack quickly. But large repositories do not usually have just one packfile: over time, fetches, pushes, maintenance tasks, and repacks can leave many packs behind.</p> <p class="wp-block-paragraph">A <a href="https://git-scm.com/docs/git-multi-pack-index">multi-pack index</a> (or MIDX) gives Git a single index over many packs. Instead of opening and searching each pack&rsquo;s individual index, Git can ask the MIDX which pack contains a given object and at which offset. This is especially useful for large repositories, and it is one of the building blocks behind GitHub&rsquo;s repository maintenance strategy.</p> <p class="wp-block-paragraph">As we covered when <a href="https://github.blog/open-source/git/highlights-from-git-2-47/#incremental-multi-pack-indexes">Git 2.47 introduced the incremental MIDX format</a>, a repository can store its MIDX as a chain of layers instead of as a single MIDX covering every pack. A single-file MIDX is simple and efficient to read, but it has an important maintenance cost; since that file includes every pack it covers, even a small update can require a large write in an already-large repository.</p> <p class="wp-block-paragraph">Incremental MIDXs address that by storing a chain of MIDX layers. Each layer covers some collection of packs, and the chain file records the order of those layers. Appending a new layer to the tip of the chain does not invalidate the older layers, so Git can index newly created packs without rewriting a single MIDX that covers the entire repository.</p> <p class="wp-block-paragraph">Git 2.55 teaches <code>git repack</code> how to write those incremental MIDX chains directly:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git repack --write-midx=incremental </code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git repack --write-midx=incremental" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">Without any other options, that mode is append-only: Git writes a new layer for the packs created by the repack and leaves the existing layers alone. That is already useful when you want to minimize how much metadata gets rewritten during a maintenance run.</p> <p class="wp-block-paragraph">But an append-only chain cannot grow forever. If each maintenance run adds a new layer, then eventually the chain itself becomes the thing you need to maintain. Git 2.55 also supports combining <code>--write-midx=incremental</code> with geometric repacking:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git repack --write-midx=incremental --geometric=2 -d</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git repack --write-midx=incremental --geometric=2 -d" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">When those modes are used together, each repack creates a new tip layer, then decides whether adjacent layers should be compacted together. The default rule is controlled by <code>repack.midxSplitFactor</code>: if the accumulated object count in newer layers grows large enough relative to the next older layer, Git merges those layers into a single replacement layer. Otherwise, the older layers are left untouched.</p> <p class="wp-block-paragraph">At a high level, the algorithm works like this. Below, <math data-latex="N"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math> refers to the <code>repack.midxNewLayerThreshold</code> value, and <math data-latex="f"><semantics><mi>f</mi><annotation encoding="application/x-tex">f</annotation></semantics></math> refers to the <code>repack.midxSplitFactor</code> value:</p> <ol class="wp-block-list"> <li>Pick the un-MIDX&rsquo;d packs as geometric repacking candidates. If the tip MIDX layer has at least <math data-latex="N"><semantics><mi>N</mi><annotation encoding="application/x-tex">N</annotation></semantics></math> packs, include those as candidates too.</li> <li>Apply the usual geometric repacking rule to that candidate set, and write a new tip MIDX layer covering the resulting packs.</li> <li>Compact adjacent MIDX layers while the accumulated object count of the newer layer(s) exceeds <math data-latex="1/f"><semantics><mrow><mn>1</mn><mi>/</mi><mi>f</mi></mrow><annotation encoding="application/x-tex">1/f</annotation></semantics></math> of the next deeper layer&rsquo;s object count.</li> </ol> <p class="wp-block-paragraph">To see how the pieces fit together, let&rsquo;s start with a repository that already has an incremental MIDX chain. The older layers are on the left, and the tip layer is on the right. Meanwhile, normal repository activity keeps producing new packs. Those packs are not covered by any MIDX layer yet, which means the next maintenance run has two jobs: decide what to repack, and decide how much of the MIDX chain to rewrite.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="256" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?resize=1024%2C256" alt="Diagram showing a chain of MIDX layers with newly written packs." class="wp-image-97078" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=2544 2544w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.36.16-PM.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">Ordinarily, those un-MIDX&rsquo;d packs are the only geometric repacking candidates: Git can write a new pack and a new tip MIDX layer without disturbing any existing layer. The figure below shows the more interesting case, where the current tip layer has accumulated enough packs to meet the configured <code>repack.midxNewLayerThreshold</code>. Once that threshold is met, packs from the tip layer can join the newly written packs as geometric repacking candidates.</p> <p class="wp-block-paragraph">Geometric repacking then asks a local question about the newest candidate packs. Geometric repacking then asks a local question about the newest candidate packs: is the pack immediately to the left of some suffix of packs (<math data-latex="\mathcal{P}"><semantics><mi class="mathcal">&#119979;</mi><annotation encoding="application/x-tex">\mathcal{P}</annotation></semantics></math>) large enough to preserve the geometric progression if Git rolls up <em><math data-latex="\mathcal{P}"><semantics><mi class="mathcal">&#119979;</mi><annotation encoding="application/x-tex">\mathcal{P}</annotation></semantics></math></em>? In the first attempt below, <em><math data-latex="\mathcal{P}"><semantics><mi class="mathcal">&#119979;</mi><annotation encoding="application/x-tex">\mathcal{P}</annotation></semantics></math></em> contains the smallest pack from the current tip layer along with the new un-MIDX&rsquo;d packs. But the pack to the left of the split is only 30,000 objects, which is smaller than twice the size of <em><math data-latex="\mathcal{P}"><semantics><mi class="mathcal">&#119979;</mi><annotation encoding="application/x-tex">\mathcal{P}</annotation></semantics></math></em>, so this split is too far to the right.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="392" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?resize=1024%2C392" alt="Diagram indicating the first geometric split is too small." class="wp-image-97082" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=2594 2594w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.39.09-PM.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">So Git moves the split one pack earlier and asks the same question again. Now <em><math data-latex="\mathcal{P}"><semantics><mi class="mathcal">&#119979;</mi><annotation encoding="application/x-tex">\mathcal{P}</annotation></semantics></math></em> includes one more pack from the tip layer. The pack immediately to the left has 100,000 objects, which is at least twice the size of the selected suffix. That is the point where the geometric invariant holds, so Git can roll up exactly those packs into a new pack.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="343" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?resize=1024%2C343" alt="Diagram showing how moving the split left finds a geometric roll-up." class="wp-image-97079" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=2676 2676w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.10-PM.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">After writing that new pack, Git writes a new tip MIDX layer over the surviving pack from the previous tip layer and the newly written roll-up pack. At this point, the packfiles themselves are in good shape, but the MIDX chain may still have accumulated too many small adjacent layers. Git applies the same &ldquo;newer compared to older&rdquo; instinct to the MIDX layers themselves: if the newer layer is large enough relative to its neighbor, compact their metadata into a replacement layer.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="420" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?resize=1024%2C420" alt="Diagram showing the new tip layer cannot compact with its neighbor." class="wp-image-97080" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=2216 2216w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.37.46-PM.png?w=2048 2048w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">That compaction step is deliberately metadata-only. Git does not repack the objects from those layers again; it writes a new MIDX layer that covers the same packfiles. Then it considers the next older layer. Here, the compacted layer is still smaller than half of the deeper layer, so Git stops. The older layer remains untouched, which is the key property that keeps this maintenance incremental.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="476" width="1024" src="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?resize=1024%2C476" alt="Diagram showing compaction stops before rewriting the deeper layer." class="wp-image-97081" srcset="https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?w=2040 2040w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/Screenshot-2026-06-25-at-6.38.26-PM.png?w=1536 1536w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">The result is a compromise between two extremes. A single-file MIDX minimizes lookup complexity, but can require large rewrites during maintenance. A purely append-only incremental MIDX minimizes each write but allows the chain to grow without bound. Geometric incremental repacking keeps the number of layers logarithmic in the total number of objects, while ensuring that the newest, smallest layers are rewritten more often than older, larger ones.</p> <p class="wp-block-paragraph">This also integrates with Git&rsquo;s existing repack machinery. Newly written packs that are not yet covered by the MIDX chain are always candidates for the geometric repack; packs in deeper MIDX layers are left alone. Packs in the tip MIDX layer join the candidate set only after the tip layer has at least <code>repack.midxNewLayerThreshold</code> packs. If the tip layer is still smaller than that threshold, Git skips disturbing it entirely and simply appends a new layer for the newly written packs.</p> <p class="wp-block-paragraph">For repositories that receive a steady stream of new objects, this means routine maintenance can update the repository&rsquo;s pack metadata incrementally, without forcing each maintenance run to rewrite a single MIDX covering the entire object store.</p> <p class="wp-block-paragraph">[<a href="https://github.com/git/git/compare/1103041f3482c2e19174a6192dabfcf6a286b6a8%E2%80%A606733a50eeec4205011d210d3932c5b708a665e9">source</a>]</p> <h2 id="h-fixing-up-earlier-commits-with-git-history" class="wp-block-heading">Fixing up earlier commits with <code>git history</code></h2> <p class="wp-block-paragraph">Anyone who has polished a commit series before sending it for review has probably had this experience: you notice that a change in your working tree really belongs in an earlier commit, not at the tip of the branch.</p> <p class="wp-block-paragraph">Today, one common way to handle that is to create a fixup commit and then autosquash it:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git commit --fixup=&lt;commit&gt; $ git rebase --autosquash &lt;commit&gt;^</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git commit --fixup=&lt;commit&gt; $ git rebase --autosquash &lt;commit&gt;^" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">That works, but it asks you to spell out the mechanism instead of the intent. Git 2.55 builds on the experimental <a href="https://git-scm.com/docs/git-history/2.55.0"><code>git history</code></a> command, <a href="https://github.blog/open-source/git/highlights-from-git-2-54/#h-rewrite-history-with-git-history">which Git 2.54 introduced</a>, by adding a new <code>fixup</code> subcommand. It applies the changes currently staged in the index to an earlier commit:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git history fixup &lt;commit&gt;</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git history fixup &lt;commit&gt;" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">Here is a small example. The first commit introduced a pancake recipe, followed by a few more commits on top. Later, we realize that the recipe was missing maple syrup. After staging that one-line change, <code>git history fixup &lt;commit&gt;</code> folds it into the original recipe commit and replays the descendant commits on top.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="369" width="1024" src="https://github.blog/wp-content/uploads/2026/06/history-fixup-demo_bf66f4.gif?resize=1024%2C369" alt="An animated gif showing Here is a small example. The first commit introduced a pancake recipe, followed by a few more commits on top. Later, we realize that the recipe was missing maple syrup. After staging that one-line change, git history fixup &lt;commit&gt; folds it into the original recipe commit and replays the descendant commits on top." class="wp-image-97109"></figure> <p class="wp-block-paragraph">Here the staged change becomes part of the target commit itself. The target commit keeps its message and authorship by default, unless you pass <code>--reedit-message</code>, and Git rewrites the commits that follow so the branch ends at an equivalent history with the fix in the right place.</p> <p class="wp-block-paragraph">Like the rest of <code>git history</code>, this command is still experimental. It is also intentionally conservative. Because <code>fixup</code> reads from the index, it needs a working tree and cannot operate in a bare repository; if applying the staged change would produce a conflict, the command aborts instead of leaving you in the middle of a stateful rewrite.</p> <p class="wp-block-paragraph">[<a href="https://github.com/git/git/compare/94f057755b7941b321fd11fec1b2e3ca5313a4e0%E2%80%A6c6c225793003ffb8b376c8994d44ca63bc04ac40">source</a>]</p> <h2 id="h-the-tip-of-the-iceberg" class="wp-block-heading">The tip of the iceberg&hellip;</h2> <p class="wp-block-paragraph">Now that we&rsquo;ve covered the largest changes in more detail, let&rsquo;s take a look at a selection of some other new features and updates in this release.</p> <ul class="wp-block-list"> <li><p>Returning readers of this series may remember <a href="https://github.blog/open-source/git/highlights-from-git-2-54/#h-config-based-hooks">our coverage of config-based hooks</a> from Git 2.54, which let you define hooks in your Git configuration rather than only as executable files in <code>$GIT_DIR/hooks</code>. Hooks are the scripts Git runs at well-known points in your workflow, like before creating a commit or after receiving a push. Moving them into configuration makes those hooks easier to share, compose, and selectively disable without copying scripts into each repository&rsquo;s hooks directory.</p><p>Git 2.55 extends that work by allowing compatible configured hooks to run in parallel. For example, a project might have independent pre-commit hooks for linting and unit tests; if both declare <code>hook.&lt;name&gt;.parallel = true</code>, Git can run them at the same time. The number of concurrent jobs can be controlled globally with <code>hook.jobs</code>, per event with <code>hook.&lt;event&gt;.jobs</code>, or on the command line with <code>git hook run -j</code>. Hooks that need shared state, like commit-message hooks or other hooks that inspect the index or working tree, continue to run serially.</p><p>[<a href="https://github.com/git/git/compare/2226ffaacd93d3fe5554687a70d9190d72596f96%E2%80%A675b7cb5e14f03965cf87a976356bcbdcfb4edbad">source</a>]</p></li> <li><p>If you have ever run <a href="https://git-scm.com/docs/git-status"><code>git status</code></a> only to be greeted by a long pause at your terminal, you may have used Git&rsquo;s <a href="https://git-scm.com/docs/git-fsmonitor--daemon/2.55.0">built-in filesystem monitor</a> to speed things back up. When <a href="https://git-scm.com/docs/git-config#Documentation/git-config.txt-corefsmonitor"><code>core.fsmonitor</code></a> is enabled, commands like <code>git status</code> can ask a long-running daemon which paths have changed instead of scanning the entire working tree.</p><p>Until now, that built-in daemon was available only on macOS and Windows. Git 2.55 adds support for Linux, where the implementation uses <a href="https://man7.org/linux/man-pages/man7/inotify.7.html"><code>inotify</code></a>. That works without elevated privileges, but requires one watch per directory, so very large repositories may need to raise the <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/fs.html#max-user-watches"><code>fs.inotify.max_user_watches</code></a> limit. As on other platforms, the daemon is conservative around network-mounted repositories, which remain opt-in.</p><p>[<a href="https://github.com/git/git/compare/d2c01318b0f04c568808072c5b328e8021b94530%E2%80%A6b1cebd7194299ad5414ab2122b2970b339399446">source</a>]</p></li> <li><p><a href="https://git-scm.com/docs/bitmap-format">Reachability bitmaps</a> are one of the tricks Git uses to answer questions like &ldquo;which objects are reachable from this commit?&rdquo; without walking the entire object graph from scratch. They make object traversals faster, but Git still has to build and update those bitmaps during maintenance tasks like <code>git repack --write-midx-bitmaps</code>.</p><p>Git 2.55 makes that generation path faster by avoiding unnecessary tree recursion, reusing already-computed selected bitmaps, caching object positions, and sorting bitmaps before <a href="https://en.wikipedia.org/wiki/Exclusive_or">XORing</a> them together. In benchmarks from the patch series, those general improvements reduced bitmap generation time in one large repository from about <a href="https://github.com/git/git/commit/e3959cc78c968d8f029daa48d4aadcb486da0629">612 seconds</a> to about <a href="https://github.com/git/git/commit/c720bbcc53f223236220c7a879f0a0e73e5d3739">294 seconds</a>.</p><p>The same series also improves <a href="https://git-scm.com/docs/gitpacking/2.55.0#_pseudo_merge_bitmaps">pseudo-merge bitmaps</a>, which group related references together so Git can combine precomputed <a href="https://en.wikipedia.org/wiki/Bit_array">bit arrays</a> during a traversal instead of rediscovering the same objects repeatedly. In <a href="https://github.com/git/git/commit/49633dc88c14008f9a405f215b60994362b36d6c">one benchmark</a>, pseudo-merges made a full <code>git rev-list --objects --use-bitmap-index</code> traversal nearly 20 times faster, but previously nearly doubled bitmap generation time. After these changes, pseudo-merges keep most of their traversal speedup while adding much less work to the bitmap generation path.</p><p>[<a href="https://github.com/git/git/compare/56a4f3c3a221adf1df9b39da69b8a6890f803157%E2%80%A65e6e8dc7860374d79bad3e2a3ade0c2d391bbad6">source</a>, <a href="https://github.com/git/git/compare/600fe743028cbfb640855f659e9851522214bc0b%E2%80%A649633dc88c14008f9a405f215b60994362b36d6c">source</a>]</p></li> <li><p>If you use partial clones, filtered packs, or other workflows where Git intentionally omits some objects, pack size still matters. The <code>git pack-objects --path-walk</code> mode, <a href="https://github.blog/open-source/git/highlights-from-git-2-51/#h-smaller-packs-with-path-walk">introduced in Git 2.51</a>, groups objects by path before performing a second compression pass, which can produce better deltas when path locality matters.</p><p>In Git 2.55, <code>--path-walk</code> can be combined with filters including <code>blob:none</code>, <code>blob:limit=&lt;n&gt;</code>, <code>tree:0</code>, <code>object:type=&lt;type&gt;</code>, <code>sparse:&lt;oid&gt;</code>, and compatible <code>combine:</code> filters. That makes packing using <code>--path-walk</code> available in more partial-clone and filtered-pack workflows. In one benchmark on Git&rsquo;s own repository, a blob-less path-walk repack produced a pack roughly 16% smaller, at the cost of a slower fresh-delta computation.</p><p>[<a href="https://github.com/git/git/compare/15dc60dcd1410a01b5e30b018895c3bd454735e5%E2%80%A6456efac53b088759abdadb6a33fa9bebdd9945b7">source</a>]</p></li> <li><p>Git learned a new experimental command, <a href="https://git-scm.com/docs/git-format-rev/2.55.0"><code>git format-rev</code></a>, for pretty-formatting revisions from standard input. Unlike <code>git log</code>, which walks a range of history, <code>git format-rev</code> is designed for cases where you encounter commits one at a time or embedded in other text.</p><p>For example, suppose you&rsquo;re using <a href="https://git-scm.com/docs/git-last-modified/2.55.0"><code>git last-modified</code></a> to print the commit that last modified each path in some directory. What if you wanted to know who last modified each path, not just which commit did it? You could replace those commits with author names by piping its output through something like this:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git last-modified | perl -F'\t' -lane ' chomp($F[0] = qx(git show -s --format=%an $F[0])); print join "\t", @F ' Junio C Hamano builtin/commit.c [...]</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git last-modified | perl -F'\t' -lane ' chomp($F[0] = qx(git show -s --format=%an $F[0])); print join &quot;\t&quot;, @F ' Junio C Hamano builtin/commit.c [...]" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">That works, but it has to start a new Git process for each row just to format the commit. In Git 2.55, git format-rev can handle that part as a normal pipeline:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git last-modified | git format-rev --stdin-mode=text --format=%an Junio C Hamano builtin/commit.c [...]</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git last-modified | git format-rev --stdin-mode=text --format=%an Junio C Hamano builtin/commit.c [...]" tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">The command&rsquo;s text mode can also rewrite full commit object names found in freeform text, which makes it useful for commit-message hooks or other scripting workflows.</p><p>[<a href="https://github.com/git/git/compare/6bfdc87e99ff67e1b850f5c3c370a810079b4f4f%E2%80%A619e3106c4510bb50c370241c06e93f050f223d5c">source</a>]</p></li> <li><p>When you push your repository somewhere, you may have noticed output that starts with <code>remote:</code>:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git push origin main Enumerating objects: 5, done. [...] remote: Resolving deltas: 100% (2/2), completed with 1 local object.</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value="$ git push origin main Enumerating objects: 5, done. [...] remote: Resolving deltas: 100% (2/2), completed with 1 local object." tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">When a client fetches or pushes, Git can multiplex different streams over the same connection: one sideband carries packfile data (the actual objects being transferred), another carries progress messages that the client usually prints to <code>stderr</code>, and a third carries errors from the remote.</p><p>Those progress messages are useful, but they also come from the other side of the connection and may be printed directly to your terminal. Before Git 2.55, that meant a server could include arbitrary terminal control sequences in sideband output, including sequences that move the cursor or erase text. Git now masks most of those control characters by default while still allowing ANSI color sequences, so colored progress output continues to work.</p><p>[<a href="https://github.com/git/git/compare/8a101334b374889938403824af956cd92e47b84d%E2%80%A6826cc4722088a02d0ae240c1267b5b74d476b153">source</a>]</p></li> <li><p>Suppose you are halfway through editing a file when you realize that you started from the wrong branch. If the branch you want to switch to changed the same path, a plain <code>git checkout &lt;branch&gt;</code> will refuse to move and risk clobbering your work. <code>git checkout -m &lt;branch&gt;</code> is the &ldquo;try to carry my local edits with me&rdquo; version of that operation.</p><p>But what happens when the other side has modifications against that same path? Previously, <code>git checkout -m</code> gave you one chance to resolve the resulting conflicts immediately. Git 2.55 makes that safer by using an autostash internally, so the conflicted local changes are saved as a stash entry that you can either resolve right away or reapply later.</p><p>[<a href="https://github.com/git/git/compare/068c10c7413fee5a69db1a46fb32f335675b25ca%E2%80%A6c07039ebc4bbf2eb6c852fb1280891a448d1bf48">source</a>]</p></li> <li><p>Some projects need to publish the same branch to more than one place, like a primary host and one or more mirrors. Remote groups have long been available to <code>git fetch</code>, where a group is configured with <code>remotes.&lt;name&gt;</code> as a whitespace-separated list of remotes. Git 2.55 lets git push use the same shorthand:</p> <div class="wp-block-code-wrapper"> <pre class="wp-block-code language-plaintext"><code>$ git config remotes.publish "github gitlab mirror" $ git push publish main</code></pre> <clipboard-copy aria-label="Copy" class="code-copy-btn" data-copy-feedback="Copied!" value='$ git config remotes.publish "github gitlab mirror" $ git push publish main' tabindex="0" role="button"><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-copy js-clipboard-copy-icon"><path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z"></path><path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z"></path></svg><svg aria-hidden="true" height="16" viewbox="0 0 16 16" version="1.1" width="16" class="octicon octicon-check js-clipboard-check-icon"><path d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0Z"></path></svg></clipboard-copy></div> <p class="wp-block-paragraph">This is equivalent to pushing to each remote in the group in sequence. Since atomicity can only be guaranteed for a single transport connection, <code>--atomic</code> is not supported when pushing to a group.</p><p>[<a href="https://github.com/git/git/compare/fca09c8fc2ccd1478cef1263b773424a0d42091c%E2%80%A68ea82816652d20ac7070a8fcd60980568a8a293c">source</a>]</p></li> <li><p><code>git log --graph</code> is great for visualizing branch structure, right up until the graph itself gets too wide to read. In repositories with many parallel branches, the graph lanes can consume most of the terminal before you get to the commit subject.</p><p>Git 2.55 adds <code>--graph-lane-limit=&lt;n&gt;</code> to <code>git log --graph</code> and related commands. Lanes beyond the limit are replaced with `~`, making graph output more manageable in repositories with very wide histories.</p><p>[<a href="https://github.com/git/git/compare/d2c01318b0f04c568808072c5b328e8021b94530%E2%80%A69bab3ce5553b2333b8f8ee1aff27a9fe6a938f65">source</a>]</p></li> <li><p>Suppose you want to list the 10 most recent commits on your branch. That is easy enough: <code>git log -n 10</code> does exactly that. But what if you want the 10 oldest commits? If you are thinking, &ldquo;it surely isn&rsquo;t <code>git log --reverse --10</code>,&rdquo; then congratulations: you&rsquo;re a veteran Git user! Instead of reversing the history and then printing 10 commits, Git takes the 10 most recent commits and reverses their order.</p><p>You can get there by post-processing the whole range (for example with <code>git log --reverse &lt;range&gt; | tail -10</code>) but doing so still asks Git to print and format all of the commits that the shell is going to throw away. Git 2.55 adds a new <code>--max-count-oldest=&lt;n&gt;</code> option to <code>git rev-list</code> and the <code>git log</code> family of commands, which selects the oldest <code>n</code> commits in a range instead.</p><p>[<a href="https://github.com/git/git/compare/e444fd1d537b40fc3061ce27d3ca46aa5ee01562%E2%80%A6ff7901eca30c308ef5a448ebd56eaf363b58a02e">source</a>]</p></li> <li><p>During a fetch, the client and server negotiate by having the client advertise commits it already has as <code>have</code> lines. That lets the server avoid sending objects the client can already reach. But in repositories with many references, the negotiation algorithm may skip a ref that is especially important for finding common history.</p><p>Git 2.55 adds new controls for which references participate in negotiation. The new include and restrict options, along with corresponding <code>remote.*</code> configuration, allow users to require certain refs to be sent as have lines or to limit negotiation to a specific set of refs.</p><p>[<a href="https://github.com/git/git/compare/ebdb4c523d5e372a6e9556d218e1cb295d23b7a2%E2%80%A6a6d92c48e4426b88a427a75ed2c20d1daa5dc7f7">source</a>]</p></li> </ul> <h2 class="wp-block-heading" id="the-rest-of-the-iceberg">&hellip;the rest of the iceberg</h2> <p class="wp-block-paragraph">That&rsquo;s just a sample of changes from the latest release. For more, check out the release notes for <a href="https://github.com/git/git/blob/v2.55.0/Documentation/RelNotes/2.55.0.adoc">2.55</a>, or <a href="https://github.com/git/git/tree/v2.55.0/Documentation/RelNotes">any previous version</a> in <a href="https://github.com/git/git">the Git repository</a>.</p> </body></html> <p>The post <a href="https://github.blog/open-source/git/highlights-from-git-2-55/">Highlights from Git 2.55</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Inside the Advisory Database and what happens when vulnerability volume breaks records - The GitHub Blog https://github.blog/?p=97150 2026-06-29T16:10:20.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">In May 2026, the GitHub Advisory Database published <strong>1,560 reviewed advisories</strong>&mdash;more than <em>five times</em> our typical monthly output and the highest in its history.</p> <p class="wp-block-paragraph">And it still wasn&rsquo;t enough to keep up.</p> <p class="wp-block-paragraph">Over the past few months, the vulnerability ecosystem has shifted in a fundamental way. Input across private vulnerability reports, repository advisories, and CVE requests has increased simultaneously, pushing the entire system to a new operating scale.</p> <p class="wp-block-paragraph">This blog builds on <a href="https://github.com/orgs/community/discussions/189802">an ongoing GitHub community discussion</a> tracking the evolving nature of vulnerability reporting, as well as PVR and Advisory Database roadmap developments. A recurring theme in that thread is the downstream impact of platform changes on advisory curation and data quality. This aligns with <a href="https://github.blog/security/raising-the-bar-quality-shared-responsibility-and-the-future-of-githubs-bug-bounty-program/">GitHub&rsquo;s broader shift</a> toward emphasizing quality and shared responsibility in vulnerability reporting, which in turn directly shapes how advisory data must be curated and maintained.</p> <h2 id="h-tl-dr" class="wp-block-heading">TL;DR</h2> <p class="wp-block-paragraph">Review times for new advisories are longer because vulnerability volume and complexity have increased significantly. Advisory quality has not changed: reviewed advisories are still human-validated, and existing alerts continue to function normally. If you want to help, focus on three things: <strong>submit complete vulnerability data, coordinate closely with maintainers and researchers, and request CVEs only when there is a clear intention to publish.</strong></p> <h2 id="h-record-output-and-unprecedented-input" class="wp-block-heading">Record output and unprecedented input</h2> <p class="wp-block-paragraph">May was not a one-time spike. From March through May, we sustained more than <strong>6,000 advisory decisions per month.</strong> This included updating existing advisories, publishing new advisories, and reviewing inbound advisories, and exceeded any prior three-month peak.</p> <p class="wp-block-paragraph">At the same time, inflow accelerated across every source:</p> <ul class="wp-block-list"> <li>Private vulnerability reports across the platform increased from ~550/week in January to more than <strong>3,000/week</strong> for most of May.</li> <li>Repository advisories scaled from ~650/week to more than <strong>5,000/week.</strong></li> <li>GitHub CNA CVE requests reached almost <strong>4,000 in May alone</strong>, nearly 10x year &ndash;over year.</li> <li>The CVE program has already published <strong>30,000+ CVEs in 2026.</strong></li> <li>More than <strong>1.7 million total repositories</strong> have enabled private vulnerability reporting.</li> </ul> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="640" width="1024" src="https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?resize=1024%2C640" alt="Line graph showing a sharp increase in PVR submissions, CNA requests, and Repo GHSAs published, starting around January 2026. The lines go back to May 2025 and is relatively flat with a slight increase until January 2026." class="wp-image-97152" srcset="https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=2560 2560w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=2048 2048w, https://github.blog/wp-content/uploads/2026/06/613839791-af0d8e48-215f-48c7-8f0c-16ef531592f7.png?w=288 288w" sizes="(max-width: 1000px) 100vw, 1000px" /></figure> <p class="wp-block-paragraph">This is not a localized surge. It reflects structural change across the vulnerability disclosure ecosystem.</p> <h2 id="h-the-impact" class="wp-block-heading">The impact</h2> <p class="wp-block-paragraph">Since mid-April, due to this surge, we have not consistently met our internal goals for publication. Processing times extended first to about a week, then to multiple weeks for a meaningful share. Longer publication times can increase exposure windows. We take that seriously, and timeliness is a core part of the value this database provides.</p> <h2 id="h-what-s-still-working" class="wp-block-heading">What&rsquo;s still working</h2> <p class="wp-block-paragraph">Our data pipelines and publishing infrastructure have continued to operate through this period. Imports are running, data integrity is intact, and published advisories are accurate. Advisories that reach reviewed status today meet the same quality standard as before.</p> <p class="wp-block-paragraph">CVE assignment quality has remained strong. Our assignment rate has held between 91&ndash;94% through the entire surge, consistent with or better than historical norms and showing that there hasn&rsquo;t been a clear degradation in the requests we receive.</p> <p class="wp-block-paragraph">The issue is throughput. The system that validates, enriches, and publishes advisory data is functioning; it is now operating beyond the volume and complexity it was designed to handle.</p> <h2 id="h-the-work-isn-t-uniform" class="wp-block-heading">The work isn&rsquo;t uniform</h2> <p class="wp-block-paragraph">Not every security advisory requires the same level of effort. Some arrive well formatted: the advisory details clearly name the affected package and its relevant ecosystem, the version range is documented, and the fix is tagged. A curator can validate and publish these in under a few minutes.</p> <p class="wp-block-paragraph">But a growing share of incoming advisories require more investigation:</p> <ul class="wp-block-list"> <li><strong>Package disambiguation.</strong> The advisory details say &ldquo;foo&rdquo;, but is that foo on npm, python-foo on PyPI, or the unrelated foo on Maven? When upstream data doesn&rsquo;t specify an ecosystem, our curators figure it out.</li> <li><strong>Version range reconstruction.</strong> Many security advisories arrive with no affected version range, or with ranges that don&rsquo;t match actual release history. Curators trace commits, changelogs, and tags to determine what&rsquo;s actually affected.</li> <li><strong>Multi-ecosystem advisories.</strong> Some projects ship packages to multiple registries, like a library with both a .NET implementation (NuGet) and a JavaScript implementation (npm) of the same functionality, where a vulnerability in the shared logic affects both. This requires independent verification across multiple data sources.</li> <li><strong>Conflicting upstream data.</strong> When the CVE record, the maintainer&rsquo;s advisory, and the commit history disagree about what&rsquo;s affected, someone has to determine the truth.</li> </ul> <p class="wp-block-paragraph">Historically more straightforward advisories dominated, and the harder ones could be absorbed. When volume surges, the queue fills with <em>both,</em> and the complex ones take disproportionately longer, creating a compounding effect. The mix now matters much more. This isn&rsquo;t just more work; it&rsquo;s significantly more complex.</p> <h2 id="h-what-reviewed-actually-means" class="wp-block-heading">What &ldquo;reviewed&rdquo; actually means</h2> <p class="wp-block-paragraph">A reviewed advisory is not simply a republished record; it&rsquo;s the result of verification.</p> <p class="wp-block-paragraph">Curators:</p> <ul class="wp-block-list"> <li>Map vulnerabilities to the correct ecosystem package</li> <li>Validate affected and fixed versions against release history</li> <li>Confirm upstream accuracy</li> <li>Check for duplication and consistency</li> <li>Validate classification and scoring</li> </ul> <p class="wp-block-paragraph">This is what allows downstream tools to rely on the data without additional validation.</p> <p class="wp-block-paragraph">Publishing faster by skipping verification would increase false positives at scale, which can create more risk than delay.</p> <h2 id="h-a-broader-ecosystem-shift" class="wp-block-heading">A broader ecosystem shift</h2> <p class="wp-block-paragraph">This trend extends beyond GitHub.</p> <p class="wp-block-paragraph">The volume of reported and published vulnerabilities continues to grow rapidly, and organizations across the ecosystem are adapting to that change.</p> <p class="wp-block-paragraph">The system is working as designed. More vulnerabilities are being reported, disclosed, and tracked than ever before. That creates pressure downstream, including during advisory curation.</p> <h2 id="h-what-we-re-doing-now" class="wp-block-heading">What we&rsquo;re doing now</h2> <ul class="wp-block-list"> <li><strong>Improving community contribution quality and throughput.</strong> Community contributions are an important part of how we improve the Advisory Database, and each is reviewed against the same validation standard as any other advisory. We&rsquo;ve strengthened triage and prioritization, so high-quality submissions are identified earlier, reviewed more consistently, and moved through the queue faster. This helps us respond to current volume while reinforcing our primary goal of maintaining a high-quality, trusted dataset.</li> <li><strong>Scaling the systems behind curation.</strong> We&rsquo;ve increased aspects of the capacity of our backend curation systems to handle higher sustained throughput and we&rsquo;re continuing to modernize the data infrastructure that supports analytics and queue management.</li> <li><strong>Building AI-assisted research tools.</strong> We&rsquo;ve developed and deployed tooling that gives our curators AI-powered assistance during the research phase of advisory review. Curators still make every decision, but routine research can be completed faster for higher quality advisories.</li> <li><strong>Expanding automation where it helps the most.</strong> We&rsquo;ve improved automation for extracting more data from upstream CVE information and for handling how community contributions interact with already-reviewed advisories. That work reduces time per decision without lowering the quality bar.</li> <li><strong>Investing in documentation and training.</strong> We&rsquo;ve significantly expanded our operational documentation. This enables us to bring new team members up to speed faster and improves consistency across the team.</li> </ul> <h2 id="h-what-we-re-building-next" class="wp-block-heading">What we&rsquo;re building next</h2> <p class="wp-block-paragraph">To support this new scale, we are investing in:</p> <ul class="wp-block-list"> <li><strong>Reducing time-per-advisory for the most common cases.</strong> A significant portion of incoming advisories require research that follows predictable patterns, such as identifying the correct package, confirming the version range, and checking for a fix. We&rsquo;re investing in tooling that accelerates these patterns, so curators can spend their time on genuinely ambiguous cases that require human judgment.</li> <li><strong>Making risk-based review prioritization smarter.</strong> We&rsquo;re exploring additional risk signals for prioritization, such as package usage, evidence of active exploitation, and ecosystem impact to ensure the advisories that matter most reach users first.</li> <li><strong>Improving the feedback loop with upstream data sources.</strong> A significant share of curation time is spent correcting incomplete or inaccurate upstream data. We&rsquo;re investing in tighter integration with the sources we ingest from, especially through increased repository GitHub Security Advisory and Private Vulnerability Reporting data validation, so that data quality issues get resolved closer to the origin rather than in our review queue.</li> <li><strong>Continuing to be transparent.</strong> We&rsquo;ll share updates on our progress as we make it. If things improve, we&rsquo;ll tell you. If we hit new challenges, we&rsquo;ll share that too.</li> </ul> <h2 id="h-what-this-means-for-you" class="wp-block-heading">What this means for you</h2> <ul class="wp-block-list"> <li><strong>Dependabot users:</strong> Existing alerts are unaffected. New advisories may take longer to trigger, with critical issues prioritized.</li> <li><strong>API and feed consumers:</strong> Reviewed data remains accurate; unreviewed advisories are visible but not yet validated.</li> <li><strong>Maintainers:</strong> Repository advisories continue to flow into the global database; prioritization is based on several factors, including project impact and severity.</li> </ul> <h2 id="h-how-you-can-help" class="wp-block-heading">How you can help</h2> <p class="wp-block-paragraph"><strong>Include complete data in vulnerability reports.</strong> Providing affected version ranges, root cause, and clear reproduction steps makes a direct difference in how quickly and accurately advisories can be reviewed. When this data is complete, curation can take minutes. When it isn&rsquo;t, curators must reconstruct missing details from source code, release history, and conflicting upstream signals. At this scale, those gaps compound quickly. High-quality upstream data is one of the most effective ways to improve both speed and accuracy across the ecosystem.</p> <p class="wp-block-paragraph"><strong>Include the right advisory details.</strong> Our <a href="https://docs.github.com/code-security/tutorials/fix-reported-vulnerabilities/write-security-advisories">best practices guide covers ecosystem categorization, package names, and version range formatting</a>, but a few additional details make a direct difference in how quickly and accurately advisories can be reviewed and published to the GitHub Advisory Database.&nbsp;</p> <ul class="wp-block-list"> <li><strong>Use the package name as it appears in the registry.</strong> Advisory package names must match the registry, not the repository or project name. Downstream systems rely on registry identifiers to match advisories to affected dependencies. If the name is incorrect or missing, alerts cannot be reliably generated, and affected users may never be notified. Using the registry name ensures the advisory can be correctly linked, indexed, and distributed.</li> <li><strong>List all affected packages.</strong> Some vulnerabilities impact multiple packages within a project. Each affected package should be listed separately with its own ecosystem, package name, and version range. Advisories are consumed at the package level, so missing a package means missing the users who depend on it. Including all known affected packages improves coverage and ensures alerts reach the full set of impacted users.</li> <li><strong>Provide a complete CVSS vector string.</strong> The GitHub Advisory Database supports CVSS <a href="https://www.first.org/cvss/v3-1/">3.1</a> and <a href="https://www.first.org/cvss/v4.0/">4.0</a>. A severity label such as &ldquo;High&rdquo; is a quick summary, but a complete CVSS vector string includes a richer set of attributes, such as attack complexity, required privileges, and user interaction, which describe the vulnerability in greater detail. This structured information allows severity to be validated, interpreted consistently, and used by downstream tools for prioritization and automation. Without it, scoring is less precise and harder to compare across advisories. If you include a score, use the official calculators and include the full vector.</li> <li><strong>Include relevant CWE classification.</strong> A CWE identifies the underlying weakness behind a vulnerability, such as cross-site scripting, SQL injection, or deserialization of untrusted data. Unlike a narrative description, a CWE gives downstream tools and security teams a standardized way to understand what kind of issue they are dealing with. That matters because CWE data can be used to categorize, filter, prioritize, and compare vulnerabilities across large datasets. It helps organizations group related issues, apply policy or reporting rules, and understand patterns in the vulnerabilities affecting their software. The more specific the CWE, the more useful the advisory becomes for downstream consumers.</li> </ul> <p class="wp-block-paragraph">For more guidance, see the <a href="https://docs.github.com/code-security/tutorials/fix-reported-vulnerabilities/write-security-advisories">best practices for writing clear, complete security advisories</a>.</p> <p class="wp-block-paragraph"><strong>Be intentional when requesting CVEs.</strong> Requesting a CVE ID signals that a vulnerability will be disclosed and tracked publicly. When requests are made without plans to publish, it can divert time and attention from advisories that are actively moving toward release. Aligning CVE requests with clear publication intent helps ensure that effort is focused on where it has the most immediate impact and keeps the system responsive for everyone.</p> <p class="wp-block-paragraph"><strong>Coordinate closely with maintainers and other researchers.</strong> High-quality advisory data depends on shared context. Aligning affected packages, version ranges, and fixes helps reduce ambiguity and conflicting information across sources. At this scale, small gaps in coordination can become large inconsistencies downstream.</p> <p class="wp-block-paragraph"><strong>Improve advisory quality by contributing pull requests to the Advisory Database.</strong> Every correction to version ranges, package mappings, or fixes improves the accuracy that developers rely on.</p> <p class="wp-block-paragraph"><strong>Recognize the scale of this ecosystem shift and take part in it.</strong> The increase in vulnerability reporting reflects real progress. More issues are being found, fixed, and disclosed than ever before. Maintaining quality at this scale depends on researchers, maintainers, and data consumers and producers working together toward the same goal.</p> <h2 id="h-the-bigger-picture" class="wp-block-heading">The bigger picture</h2> <p class="wp-block-paragraph">Two years ago, the database published ~270 advisories per month.</p> <p class="wp-block-paragraph">In May 2026, it published over 1,500 while processing thousands of additional decisions across the system.</p> <p class="wp-block-paragraph">This reflects a broader shift:</p> <ul class="wp-block-list"> <li>More repositories are enabling responsible disclosure.</li> <li>More researchers are reporting vulnerabilities.</li> <li>More maintainers are publishing fixes and advisories.</li> <li>The vulnerability ecosystem is scaling toward greater transparency.</li> </ul> <p class="wp-block-paragraph">That growth creates pressure on systems like ours. But it also represents meaningful progress.</p> <p class="wp-block-paragraph">Every advisory improves visibility. Every alert reduces risk.</p> <p class="wp-block-paragraph">We are scaling to meet that reality, and we will continue to share progress as we do.</p> </body></html> <p>The post <a href="https://github.blog/security/supply-chain-security/inside-the-advisory-database-and-what-happens-when-vulnerability-volume-breaks-records/">Inside the Advisory Database and what happens when vulnerability volume breaks records</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> GenPage: Towards End-to-End Generative Homepage Construction at Netflix - Netflix TechBlog - Medium https://medium.com/p/77146fba8a08 2026-06-29T13:01:02.000Z <p>Authors: <a href="https://www.linkedin.com/in/lequn-luke-wang-9226b2129/">Lequn Wang</a>, J<a href="https://www.linkedin.com/in/jiangwei-pan-66a62a13/">iangwei Pan</a>, and <a href="https://www.linkedin.com/in/linasbaltrunas/">Linas Baltrunas</a></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*NurrizMgC7_QbsGuuW42Dg.gif" /><figcaption><strong>Figure 1. </strong>Autoregressive homepage generation. GenPage builds a Netflix homepage one row or entity at a time, each one conditioned on what’s already on the page and the user’s context.</figcaption></figure><h3>Introduction</h3><p>The Netflix homepage is the first thing users see when they open the app and the primary way they discover content to enjoy. Almost every part of it is personalized, including which rows appear, which entities show up within those rows, and how everything is arranged on the page.</p><p>Constructing that homepage is a genuinely hard problem. It is not simply producing one ranked list. The homepage is a structured, two-dimensional layout, made up of recommendation rows and the entities within them. Here, an entity can be a movie, show, game, live event, or other recommendable item. Each choice can affect the value of the others. Traditionally, it is built through a complex, multi-stage pipeline, with separate components for candidate generation and ranking at both the row and entity levels.</p><p>We saw an opportunity to rethink this design. Large language models have shown that a single generative model can perform diverse tasks just by generating a response to a prompt. Inspired by this prompt-response paradigm, we trained a single generative model to build the homepage by directly answering one question:</p><blockquote>Given everything we know about this user and this request, what homepage should we generate to maximize user satisfaction?</blockquote><p>We call this approach GenPage. It treats the user history and request context as the prompt, and autoregressively generates the entire homepage as the response (Figure 1). Unlike most generative recommenders, such as<a href="https://arxiv.org/abs/2305.05065"> TIGER</a>,<a href="https://arxiv.org/abs/2402.17152"> HSTU</a>, and<a href="https://arxiv.org/abs/2506.13695"> OneRec</a>, which generate flat ranked lists, GenPage generates the rows, entities, and layout together.</p><p>This shift is motivated by several goals:</p><ul><li><strong>End-to-end modeling.</strong> A single transformer model that constructs the page from raw input signals can replace a complex multi-stage recommender stack. This reduces the number of ML models to maintain, avoids misaligned objectives across stages, and eliminates much of the traditional feature engineering.</li><li><strong>Whole-page optimization via reinforcement learning (RL).</strong> Autoregressive page generation makes it possible to optimize for page-level rewards with RL. This can capture interactions across rows and entities, such as diversity or the balance between rows with different <em>stopping power</em>. For example, a Continue Watching row near the top of the page may strongly satisfy a user’s immediate intent, but also reduce how much of the page they browse. Modeling these interactions at the page level lets us align the system more directly with user satisfaction than entity-level objectives alone.</li><li><strong>Better scaling behavior.</strong> A generative transformer model gives us a clearer path to improving quality through more data, compute, and model capacity, without repeatedly redesigning the system.</li><li><strong>Flexibility and extensibility.</strong> The prompt-response paradigm is flexible by design. By simplifying feature engineering and enabling whole-page optimization, GenPage makes it easier to support new product experiences, such as additional content types like live events, games, and podcasts; layouts beyond the current two-dimensional structure; personalized UI components; and per-entity artwork personalization, all with fewer architectural changes.</li></ul><p>Bringing GenPage into production at Netflix also required solving challenges specific to industry-scale recommender systems. Because the homepage is generated in real time, serving latency is a primary engineering constraint. We also need to handle entity cold start in a constantly evolving catalog, keep the model fresh as user interests and cultural trends shift, and enforce complex product and business rules on the generated output.</p><p>Despite these challenges, GenPage has already had substantial production impact. In an online A/B test against a mature, highly optimized multi-stage production recommender, GenPage delivered statistically significant gains on the core user engagement metric we use for launch decisions, while reducing end-to-end serving latency by 20%.</p><p>Offline, two findings stood out. First, enriching the prompt helped more than scaling model capacity in our current regime. Second, RL post-training increased homepage diversity even though diversity was not part of the objective.</p><p>We expect this approach to generalize to many personalization settings. In this post, we focus on Netflix homepage construction as a concrete case study, sharing our design, trade-offs, and lessons learned.</p><h3>Data</h3><p>Moving from a traditional recommender to a generative transformer requires us to rethink how the data is represented. Similar to how an LLM turns text into tokens, GenPage represents both the user context and the generated homepage as one sequence of discrete tokens (Figure 2). This sequence includes the full structured homepage layout, with multiple rows and the entities inside them, so the model can generate the page holistically rather than scoring each row or entity in isolation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RL-TMAFPo93aO3GocVBBvQ.png" /><figcaption><strong>Figure 2. </strong>Tokenization of Netflix homepage construction data. The context tokens function as the prompt, drawing from diverse data sources including user history, profile attributes, and request context, with example tokens shown for each source. The page tokens represent the generated response, encoding the structured layout of rows and entities.</figcaption></figure><p>Each training example represents a homepage impression and consists of three components:</p><ul><li><strong>Context: </strong>user engagement history, profile attributes, and request context.</li><li><strong>Page:</strong> the recommended rows and entities shown on the homepage, in layout order.</li><li><strong>Feedback:</strong> user interactions with that page, such as play, thumbs-up, or abandonment for entities on the page.</li></ul><p>Only the context and page are tokenized as model inputs and outputs. Feedback is used to derive supervision signals via our internal reward system (see the Reward system section).</p><p>Instead of using an off-the-shelf text tokenizer, we build a domain-specific tokenizer for the homepage construction data. This is a proven approach in<a href="https://arxiv.org/abs/1808.09781"> recommender systems</a> and other specialized domains including<a href="https://arxiv.org/abs/2102.12092"> computer vision</a>,<a href="https://www.pnas.org/doi/10.1073/pnas.2016239118"> biology</a>, and<a href="https://arxiv.org/abs/1811.02633"> chemistry</a>, where the raw data is not naturally represented as text. Compared with generic text tokenization, this gives us two key advantages:</p><ul><li><strong>Computational efficiency.</strong> Custom tokenization significantly reduces sequence length, lowering inference cost and latency. For example, representing the event “User watched Orange Is the New Black for 50 minutes 30 days ago.” would require 16 tokens with the GPT-5 tokenizer, whereas our scheme compresses it to 4 tokens: [Entity_ID], [Action_Type], [Action_Time_Bucket], and [Action_Duration_Bucket].</li><li><strong>Product control.</strong> A direct mapping between tokens and product concepts, such as rows and entities, makes it easier to control what the model can generate. This is crucial for enforcing business rules on the final homepage.</li></ul><h4>Context tokens</h4><p>Context tokens encode user engagement history, user profile, and request context.</p><p>We represent user history as a sequence of user actions. For each action, we extract key metadata, including the action type, entity ID, timestamp, and duration. These actions include both explicit signals, such as play, add to My List, and thumbs-up, and implicit signals, such as trailer views or visits to a details page.</p><p>User profile tokens capture attributes such as language and profile type. Request context tokens encode signals like time of day, day of week, and device.</p><p>Some data sources are too long to include directly as raw token sequences. A user’s full impression history, for example, would be prohibitively expensive to represent in full. In these cases, we use a summarized version. This is a pragmatic trade-off: while GenPage aims to operate on raw inputs as much as possible, handcrafted summaries still introduce a form of prompt engineering into the pipeline. Learning to compress these long data sources end to end is an important direction for future work.</p><p>To help the model distinguish between data sources, we insert special tokens that mark the start of each segment. Continuous signals, such as timestamps and durations, are bucketized into discrete ranges to keep the vocabulary finite.</p><h4>Page tokens</h4><p>Each entity, such as a show, movie, or game, and each row, such as Korean TV Shows, is represented as a single token. The homepage is serialized in layout order: left to right, then top to bottom. We update the entity and row vocabulary daily to incorporate newly added entities and rows. Entities that are still out of vocabulary at serving time are handled through semantic embedding fusion and fallback tokens, both described later.</p><p>In principle, the same paradigm can extend to any output that can be expressed as a linear token sequence. This includes layouts beyond the current two-dimensional structure, such as one-dimensional feeds or mixed layouts, as well as personalized UI components and per-entity outputs such as personalized artwork. We leave these extensions to future work.</p><h4>Paginated recommendation</h4><p>To make recommendations responsive to in-session user preferences, the homepage is often generated incrementally, a few rows at a time. Before each pagination request, we append the page tokens from previously generated rows to the prompt, along with the user’s latest engagements on those rows from Netflix’s real-time event-logging infrastructure. This allows the model to generate the next set of recommendations using both the user’s long-term preferences and their most recent in-session behavior.</p><h3>Reward system</h3><p>To quantify the long-term value of a recommendation, we rely on an internal reward system described in<a href="https://dl.acm.org/doi/10.1145/3604915.3608873"> prior work</a>. The reward system is tuned through online A/B testing to align with long-term user satisfaction and serves as the primary supervision signal for both supervised and reinforcement learning.</p><p>The reward system processes user feedback and assigns a scalar reward for every impressed entity on the homepage. For instance, a TV show binge-watched in one night reflects stronger user satisfaction and receives a higher reward than a movie watched for only 10 minutes. An impressed entity that the user abandons receives a negative reward.</p><p>We define the page-level reward as the sum of rewards across all impressed entities on the homepage.</p><h3>Model architecture</h3><p>GenPage uses a standard decoder-only transformer architecture, the same general architecture behind many modern LLMs. This choice keeps the model simple and flexible, while also letting us benefit from the broad ecosystem of tooling around transformer training and serving.</p><p>One architectural detail is that we untie the input embedding and output projection weights. This is useful because pretraining and post-training place different demands on the logits. Next-token prediction pretraining optimizes a softmax over the vocabulary, while weighted binary classification (WBC) post-training optimizes per-token sigmoid scores, as described below. Untying the weights gives the model more flexibility to adapt to both objectives.</p><h3>Training recipe</h3><p>Our training pipeline mirrors the LLM recipe: we first teach the model the “language” of the Netflix homepage through pretraining, then align its outputs with user satisfaction through post-training. For post-training, we explore two alternative approaches: weighted binary classification (WBC) and reinforcement learning (RL).</p><p>WBC is simpler to optimize and aligns directly with the entity-level objectives of our production ranking models. RL is harder to evaluate and optimize, but it is the key path to GenPage’s full vision of page-level optimization, with the flexibility to incorporate test-time reasoning and multi-token entity representations.</p><h4>Pretraining via next-token prediction</h4><p>We pretrain the model with a standard next-token prediction objective: given the context tokens and a prefix of page tokens, the model learns to predict the next page token. This stage focuses on representation learning, teaching the model the relationship between user contexts and successful homepages. Note that our context-page training examples resemble the prompt-response pairs used in LLM supervised fine-tuning (SFT) more than the raw text used in LLM pretraining. We nonetheless call this stage <em>pretraining</em> because we train the model from scratch rather than fine-tuning from an existing checkpoint.</p><p>Unlike LLMs, which often face a scarcity of high-quality labeled data, recommender systems have an abundance of user feedback. For pretraining, we use homepage impressions that received positive feedback when served in production, bootstrapping the model to generate pages similar to those produced by the existing production system.</p><p>However, pretraining mainly teaches GenPage to imitate the production system. It does not directly optimize the magnitude of the reward, and as GenPage becomes part of production, repeatedly training on pages generated by earlier versions of the model can risk <a href="https://www.nature.com/articles/s41586-024-07566-y">model degeneration</a>. To address these limitations, we explore two post-training approaches.</p><h4>Post-training via weighted binary classification</h4><p>One effective way to align the generative model with user satisfaction is weighted binary classification (WBC). At a high level, WBC turns generation into token-level value prediction: given the user context and the tokens generated so far, the model learns to estimate the value of generating each possible next row or entity token.</p><p>This objective is easier to optimize than page-level RL. By decomposing the homepage into per-token targets, WBC provides token-level credit assignment by construction, rather than requiring RL to infer how each generated decision contributed to the final page-level reward.</p><p>This training setup is enabled by our custom tokenization. Each page token corresponds directly to a specific entity or row, making it straightforward to assign a reward. For every impressed entity on the page, our reward system provides a scalar reward based on user feedback. For each impressed row, we derive a row-level reward by aggregating the rewards of the entities in that row.</p><p>From each reward, we derive a binary label from its sign, such as positive engagement versus abandonment, and a weight from its magnitude, such as binge-watching receiving a higher weight than a short play. We then optimize a weighted binary cross-entropy loss on the logit for the corresponding token. Under this setup, the logit for a token can be interpreted as the model’s value estimate for generating that token at that position.</p><p>Although the model is trained as a value predictor, it can still generate pages autoregressively. At each step, the model scores the candidate next tokens, greedily selects the token with the highest value, and appends it to the prefix. This process repeats token by token until the full homepage is generated.</p><h3>Post-training via reinforcement learning</h3><p>Our second post-training approach is reinforcement learning (RL). WBC is effective for optimizing entity-level metrics, but it does not directly optimize the homepage as a whole. RL treats page generation as a sequential decision-making problem, allowing the model to optimize a page-level reward while preserving the flexibility of autoregressive generation.</p><p>This opens the door to several important capabilities:</p><ul><li><strong>Whole-page optimization.</strong> RL directly optimizes an aggregate page-level reward, allowing the model to account for interactions across rows and entities, such as diversity, stopping power, and page-level business constraints.</li><li><strong>Test-time reasoning.</strong> Analogous to its application in LLMs, RL can optimize reasoning capabilities for generative recommendation. Reasoning outputs can also be viewed as a form of automated feature engineering.</li><li><strong>Multi-token entity support.</strong> In our current tokenization, each entity and row is represented as a single token, so rewards map cleanly to individual tokens. In more complex settings, however, an entity may require multiple tokens, such as [Show_ID] plus [Episode_#] for an episode, or a sequence of <a href="https://arxiv.org/abs/2305.05065">semantic ID</a> tokens. In that case, WBC’s per-token labeling becomes ambiguous because a single entity-level reward must be distributed across multiple tokens. RL avoids this issue by optimizing the sequence-level return, making it a more natural fit for variable-length, multi-token entities.</li></ul><p>Inspired by the<a href="https://arxiv.org/abs/1706.03741"> RLHF</a> recipe used to align large language models, we adopt a two-step approach. First, we train a reward model that predicts the page-level reward for a generated page. This reward model is distinct from the reward system described earlier. The reward system converts <em>observed</em> user feedback into a scalar reward for a page that was actually shown, whereas the reward model <em>predicts</em> the page-level reward for a generated page without showing it to the user. This prediction is what lets RL optimize against arbitrary candidate pages during training.</p><p>Training against a reward model avoids the high variance of off-policy correction on logged or predicted propensities, but introduces the risk of reward hacking. Since the reward model is trained on data generated from the production policy, it is most reliable on pages similar to those the production policy generates. We therefore use a KL penalty to keep the policy close to the pretrained checkpoint, which itself was trained to mimic the production policy. This keeps the pages within the reward model’s region of coverage and limits opportunities for reward hacking.</p><p>For the RL algorithm, we adopt<a href="https://arxiv.org/abs/2503.20783"> Dr. GRPO</a>, a variant of<a href="https://arxiv.org/abs/2501.12948"> GRPO</a> that mitigates biases in the training objective. To train the model within this framework, we need the following components:</p><ul><li><strong>Prompts:</strong> production user requests, represented by context tokens.</li><li><strong>Policy and reference models:</strong> both are initialized from the pretrained checkpoint; the reference model anchors the KL penalty discussed above.</li><li><strong>Reward model:</strong> a dedicated transformer-based reward model, also initialized from the pretrained checkpoint, predicts the page-level outcome reward, using the sum of entity-level rewards from our internal reward system as the supervision target. We also incorporate rule-based format rewards to guide the RL policy. For example, the page should resemble a list of rows, and business-critical rows or entities should not appear too low on the page.</li></ul><h3>Addressing production challenges</h3><h4>Cold start</h4><p>New entities lack the rich interaction data needed to learn robust token embeddings. We address this through two complementary strategies:</p><ul><li><strong>Context injection. </strong>We inject metadata about new or time-sensitive entities (e.g., Live Now events) directly into the context tokens, providing the model with semantic and time-sensitive information.</li><li><strong>Semantic embedding fusion.</strong> Rather than relying solely on entity ID embeddings learned from user interaction data, we represent each entity as a fusion of its ID embedding and a content-based embedding derived from semantic information such as synopses, cast, transcripts, genres, and video content. This fused embedding serves as the input embedding for the entity’s token in the transformer. During training, with small probability, we randomly replace an entity ID token with the generic fallback token (described below), so the model learns to make recommendations from the content-based embedding alone. This ensures that a new entity has a meaningful representation in the same latent space as established entities as soon as its content metadata is available — even before it has any interaction data.</li></ul><h4>Multi-cadence incremental training</h4><p>At Netflix scale, daily retraining of a large transformer from scratch is prohibitively expensive, but recommendation models must remain fresh to capture shifting trends and new catalog additions. We address this with a multi-cadence incremental training strategy (Figure 3).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*3Ybwr7_r1reMnPQTYIsTuA.png" /><figcaption><strong>Figure 3.</strong> Multi-cadence incremental training. Periodic large-scale pretraining and post-training passes run on a broad historical window. Between them, daily incremental updates combine the latest day’s data with a sampled subset of past data to keep the model fresh while avoiding catastrophic forgetting.</figcaption></figure><p>Our training pipeline operates on a cyclic schedule with two distinct rhythms. At a tunable cadence, we conduct a large-scale pretraining and post-training pass on data from a broad historical window. Between these passes, each day we perform an incremental update by continuing post-training from the previous day’s checkpoint, using a mix of the latest day’s data and a sampled subset of past data. This helps the model stay current with new trends and catalog changes while preventing overfitting and <a href="https://arxiv.org/abs/1612.00796">catastrophic forgetting</a>.</p><p>To manage the daily influx of new tokens (e.g., new entities, rows), we employ fallback tokens. New tokens are initialized using fallback tokens of their type (e.g., [Row_Fallback_Token] for new rows, [Entity_Fallback_Token] for new entities). During training, we randomly replace a small percentage of known tokens with fallback tokens, teaching the model to handle unknown tokens gracefully.</p><h4>Enforcing business rules</h4><p>A Netflix homepage must satisfy structural constraints (e.g., organized as a list of rows) as well as product logic such as deduplication, row pinning, and category consistency (e.g., entities in a Comedy row must be comedies). While training signals can encourage rule adherence, they cannot guarantee strict compliance.</p><p>We enforce these rules at inference time through <em>constrained decoding</em>. At each autoregressive generation step, we compute a mask of eligible tokens based on the applicable business rules and apply it to the output logits, allowing only rule-compliant tokens to be generated. This is greatly simplified by our custom tokenization: because each entity and row is a single token, business rules map directly to token-level masks, avoiding the multi-token bookkeeping that constrained decoding requires over a text vocabulary. For example, to pin a specific row (e.g., popular games) at a fixed position (e.g., row position 2), we simply mask out all other tokens at that position.</p><h4>Hybrid row decoding</h4><p>Autoregressive generation ensures that each newly generated token is conditioned on the full preceding context, but generating every entity token one at a time can be expensive. We leverage the structure of the homepage to balance inference efficiency with the amount of contextual information available to each generated token.</p><p>Within each row, the first few entities are especially important: they receive the most user attention and strongly shape the row’s perceived quality and theme. To reduce inference latency, we use a hybrid row decoding strategy. The model autoregressively generates only the first few entities in each row. Conditioned on this generated prefix, we obtain logits for all eligible entities in a single forward pass and select the top-scoring remaining entities, subject to the same inference-time business-rule constraints described above.</p><p>This approach preserves autoregressive conditioning where it matters most while avoiding the latency and cost of decoding long rows token by token.</p><h3>Offline experiments</h3><p>We ran a series of ablations on Netflix internal data to understand how different components of GenPage affect model quality. Because the system was developed iteratively, individual ablations span different training configurations and data snapshots, so we report only relative comparisons within each study. Unless otherwise noted, experiments use ~200M-parameter models and report results on a held-out evaluation set.</p><h4>Does pretraining help?</h4><p>We compare WBC post-training with and without a preceding next-token-prediction pretraining stage. Figure 4 shows that pretraining yields substantial improvements across all metrics.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*o-dD9HXsqX9OQIhtxW4Qhg.png" /><figcaption><strong>Figure 4.</strong> Relative improvement from pretraining (versus WBC post-training without a pretraining stage), across loss reduction, row AUC lift, and entity AUC lift. Loss is the weighted binary cross-entropy; Row and Entity AUC are sample-weighted ROC-AUC over row and entity targets.</figcaption></figure><p>The gains may look small in absolute terms, but they are large in our production regime: setting aside the sample weighting, an Entity AUC lift from 0.91 to 0.92 means that for a randomly drawn pair of impressed entities, the model’s misranking rate drops from 9% to 8% — a magnitude of improvement we rarely observe from a single change on a mature production system. Pretraining the model on the “language” of the Netflix homepage provides a strong initialization for post-training, mirroring the pretrain-then-post-train recipe behind modern LLMs.</p><h4>How does performance scale with model size?</h4><p>We sweep model size from ~120M to ~900M parameters (Figure 5) and report the next-token-prediction loss from pretraining and the WBC loss from post-training. Both losses decrease in a power-law-like fashion, mirroring the <a href="https://arxiv.org/abs/2001.08361">scaling trends seen in LLMs</a>. This confirms that the generative approach scales favorably with model size, suggesting that recommendation quality can be further improved by scaling capacity.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CUwhWQhd3dXu2hoUMLwOyw.png" /><figcaption><strong><em>Figure 5. </em></strong><em>Pretraining and WBC post-training losses as model size scales from 120M to 900M parameters. Both decrease in a power-law-like fashion, mirroring LLM scaling trends.</em></figcaption></figure><h4>How does performance scale with information in the user context?</h4><p>Over the course of development, we progressively enriched the prompt, both by adding new data sources to the context and by refining how each source is tokenized. With model size held fixed, the WBC post-training loss decreases substantially as the context is enriched (Figure 6).</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-oMxiSFxv1I2cv6tOgPljg.png" /><figcaption><strong>Figure 6.</strong> WBC post-training loss as we progressively enrich the user context tokens. Loss is normalized to the first step (= 1.0).</figcaption></figure><p>The model-size sweep and the context-enrichment sweep span different axes and are not strictly comparable: the model-size study covers roughly an order of magnitude in parameters, while the context study spans the full trajectory of our prompt design. Even so, the gap between the two is striking. Scaling the model from 120M to 900M parameters reduces WBC loss by roughly 1.3%, whereas the cumulative effect of enriching the context is around 6.9%. In several cases, a single well-designed context addition delivers a larger improvement than the entire ~7.5× model-capacity scaling.</p><p>This suggests that, in our regime, enriching the prompt — both what we put in the context and how we tokenize it — yields a substantially larger improvement than scaling model capacity. Personalization quality appears to be bottlenecked first by the information and representation available to the model, and only then by capacity. We expect context enrichment to dominate until the context is saturated, at which point model capacity becomes the primary driver.</p><h4>Does RL post-training optimize at the page level?</h4><p>In offline evaluations (Figure 7), RL post-training consistently improves the page-level reward over the pretrained checkpoint, but this is largely confirmatory: the reward is computed using the same model the policy is optimizing against. More interestingly, although diversity is not part of the RL objective, homepage diversity — measured via pairwise embedding distance among entities on the page — also increases over the course of training. This suggests that the RL-trained policy is optimizing the page as a whole rather than myopically optimizing each token in isolation.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QJoEYMljuWOh1wj2mCdI6Q.png" /><figcaption><strong>Figure 7.</strong> RL post-training dynamics. Reward and diversity are shown relative to the initial checkpoint (1.0). Reward rises as expected; diversity also rises, despite not being part of the RL objective.</figcaption></figure><h3>Online evaluation</h3><p>We conducted an online A/B test against the current production homepage recommender using GenPage. In this test, GenPage decoded over the existing production row and entity candidate sets, which help handle many business rules (such as eligibility).</p><p>Figure 8 shows the result: all variants delivered statistically significant improvements on the core user engagement metric we use for launch decisions (p &lt; 0.001) against a mature, highly optimized multi-stage production baseline. The variants differed in their training-data configurations; that they all delivered comparable lifts suggests the gain is robust to these design choices rather than dependent on a particular configuration.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/806/1*q9qLegd75SRjC05X15gWww.png" /><figcaption><strong><em>Figure 8. </em></strong><em>Daily core user engagement metric over a 14-day online A/B test. The figure shows the average treatment effect of several GenPage variants (differing in training-data configurations) against the production baseline. Shaded regions are 95% confidence intervals. All variants delivered statistically significant improvements over production.</em></figcaption></figure><p>Alongside the engagement wins, we observed unintended shifts in the distribution of impressed entity categories (e.g., new vs. established titles, TV shows vs. movies). These shifts are not necessarily negative, but they are not something we explicitly optimized for, and they warrant deeper investigation. We suspect these shifts reflect GenPage personalizing more precisely than the production stack — consistent with an increase in homepage impression efficiency, i.e., users engaging with what they saw using fewer impressions. This sharper personalization appears to surface production-inherited components (such as the reward system) that aren’t yet aligned with the new generative paradigm. We plan to characterize the drivers of these shifts and, where appropriate, tune these components so the resulting distributions better align with desired product behavior.</p><p>We also observed strong responsiveness to in-session signals: the latest in-session actions quickly influenced subsequent recommendations and faded back to long-term preferences after a day or two, confirming that the model effectively attends to action timestamps. This responsiveness emerges naturally from the generative formulation, without the extensive manual feature engineering used in our production stack.</p><p>Contrary to the common assumption that generative models are slower, GenPage reduced end-to-end serving latency by 20% relative to the baseline. By replacing multiple ranking stages and heavy feature computation with a single transformer operating on raw tokenized inputs, we eliminated substantial serving complexity and computational overhead. Custom tokenization and hybrid row decoding further reduced the number of decoding steps, and thus latency. The 20% reduction was achieved without exhausting the available optimizations; further reductions are possible, and this headroom can be reinvested in capacity or richer prompts.</p><h3>Conclusion</h3><p>We presented GenPage, an early step toward end-to-end generative Netflix homepage construction: representing user context as a tokenized prompt and generating the entire homepage autoregressively in real time. This collapses the traditional multi-stage recommender stack into a single transformer that can be optimized end-to-end.</p><p>In online A/B tests against a mature, highly optimized multi-stage production system, GenPage delivered statistically significant gains on the core user engagement metric we use for launch decisions, while reducing end-to-end serving latency by 20%. Achieving this required adapting the LLM training recipe — pretraining followed by WBC or RL post-training — together with a set of domain-specific techniques: custom tokenization for serving efficiency and product control, context injection and semantic embedding fusion for entity cold start, multi-cadence incremental training for model freshness, constrained decoding for business-rule enforcement, and hybrid row decoding for inference efficiency.</p><p>Two offline findings stand out. First, in our current regime, enriching the prompt yields a substantially larger improvement than scaling model capacity — a takeaway we expect to generalize to other industry-scale personalization settings, at least until the available context is fully exploited. Second, RL post-training increases homepage diversity even though diversity is not part of the objective — an indication that page-level optimization captures interactions across rows and entities.</p><p>Several pieces of the full vision are still in progress: long context still relies on handcrafted summarization, and broader LLM-style capabilities — language, multimodality, and reasoning — have not yet been incorporated. One promising direction here is a hybrid tokenization combining our domain-specific tokens with generic text tokens, retaining structured control while inheriting the strengths of general-purpose LLMs; conceptually, this introduces an additional recommendation modality into an LLM.</p><p>More broadly, we expect many advances from the LLM ecosystem to transfer naturally to this setting, and the boundary between an LLM and a recommender system may increasingly blur. Our results suggest this is a viable path toward simpler recommender systems that align more directly with user satisfaction.</p><h3>Acknowledgments</h3><p>Contributors to this work (in alphabetical order): Abhishek Agrawal, Baolin Li, Casey Stella, Daneo Zhang, Dan Zheng, Donnie DeBoer, Fengdi Che, Fernando Amat Gil, Grace Huang, Inbar Naor, Ishita Verma, Jason Uh, Jimmy Patel, Justin Basilico, Lanxi Huang, Lingyi Liu, Liping Peng, Louis Wang, Michelle Kislak, Nathan Kallus, Nicolas Hortiguera, Paran Jain, Qusai Al-Rabadi, Rein Houthooft, Ryan Lee, Santino Ramos, Scarlet Chen, Shaojing Li, Sheallika Singh, Si Cheng, Wei Wang, and ZQ Zhang.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=77146fba8a08" width="1" height="1" alt=""><hr><p><a href="https://netflixtechblog.com/genpage-towards-end-to-end-generative-homepage-construction-at-netflix-77146fba8a08">GenPage: Towards End-to-End Generative Homepage Construction at Netflix</a> was originally published in <a href="https://netflixtechblog.com">Netflix TechBlog</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p> GitHub and UNDP team up to advance development priorities in Ghana with open source - The GitHub Blog https://github.blog/?p=97118 2026-06-26T16:53:57.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">Open source software is commonplace. Many people use it without even knowing, whether for everyday web browsing or building tools to improve efficiency. At its core, open-source software is built on code that is publicly available for anyone to use, adapt, and improve. It&rsquo;s more complex, however, when a government sets out to adopt open source at scale to drive development impact.</p> <p class="wp-block-paragraph">This discussion is happening in Ghana, where the Ministry of Communications, Digital Technology, and Innovation (MoCDTI) is undertaking an ambitious and <a href="https://moc.gov.gh/2026/03/11/ghana-is-undergoing-a-deliberate-digital-reset-under-his-excellency-president-mahamas-leadership-hon-nartey-goerge/https://moc.gov.gh/2026/03/11/ghana-is-undergoing-a-deliberate-digital-reset-under-his-excellency-president-mahamas-leadership-hon-nartey-goerge/">deliberate digital reset</a> to create more jobs, grow enterprises, and empower youth. This effort includes advancing more than a dozen legislative reforms at once, covering areas such as cybersecurity, data protection, electronic communications, data exchange and emerging technologies. Some are new legislation, while others replace existing laws. Together, they are redefining the country&rsquo;s ICT legal framework.</p> <p class="wp-block-paragraph"><strong>What does long-term sustainability look like in practice?</strong> Will the systems built on these foundations be open and auditable? Or will they end up trapped behind proprietary walls, with a limited number of vendors able to assess and support them?</p> <h2 id="h-governments-pivot-to-open-source-adoption" class="wp-block-heading">Governments pivot to open source adoption</h2> <p class="wp-block-paragraph">For governments and other large organizations, making a strategic shift towards adopting open source is a significant undertaking that presents both challenges and opportunities.</p> <p class="wp-block-paragraph">A ministry might deploy an open-source tool for a particular project, however systems-wide questions on managing licensing compliance, building internal maintenance capacity and coordinating across other ministries within a government may be unanswered. As a result, this adoption of open source, whilst transformative in value, could remain in isolation&ndash;without ever becoming the institutional default.</p> <p class="wp-block-paragraph">This is the gap the OSPO (Open Source Programme Office) model is designed to fill. OSPOs are a common setup in the private sector, providing structured governance for open source: policies, compliance, community engagement, and skills development. The model is now <a href="https://www.undp.org/digital-innovation/osee">increasingly being adopted</a> by universities, civil society, and within the public sector that has underscored the need for readiness to ensure effective adoption.</p> <p class="wp-block-paragraph">The <strong>Open Source Programme Office Readiness Assessment</strong>, known as OSPORA, is a UNDP-led initiative that does exactly this for countries. Supported by the government of France, OSPORA is a structured diagnostic approach that helps governments assess their readiness for open source adoption and governance. Critical for identifying practical steps, it could be thought of as the equivalent of running an audit before an architecture migration except the architecture is institutional, not technical.</p> <p class="wp-block-paragraph">OSPORA asks: What policies exist? What&rsquo;s the technical capacity? Who are the internal champions? Where are the coordination failures? Does procurement deter open source adoption? And crucially: what&rsquo;s politically realistic given the current government&rsquo;s priorities?</p> <h2 id="h-ghana-demonstrating-what-s-possible" class="wp-block-heading">Ghana demonstrating what&rsquo;s possible</h2> <p class="wp-block-paragraph">Early last month in Ghana, the GitHub Policy team teamed up with UNDP to carry out of these assessments. Over the course of a week, the team ran interviews and workshops with diverse stakeholders, including:</p> <ul class="wp-block-list"> <li><strong>Senior officials at MoCDTI, including the National IT Agency and the Kofi Annan-India Center of Excellence</strong><strong>,</strong> who are leading the digital transformation and legislative reform process</li> <li><strong>Heads of IT departments</strong> across government ministries</li> <li><strong>Community tech groups</strong> building open source within Ghana&rsquo;s developer ecosystem</li> <li><strong>The local Linux user group</strong>, which bridges global open source governance, community and local implementation</li> </ul> <figure class="wp-block-image size-full"><img data-recalc-dims="1" fetchpriority="high" decoding="async" width="800" height="533" src="https://github.blog/wp-content/uploads/2026/06/image006.jpg?resize=800%2C533" alt="A speaker walks among the audience at the workshop." class="wp-image-97120" srcset="https://github.blog/wp-content/uploads/2026/06/image006.jpg?w=800 800w, https://github.blog/wp-content/uploads/2026/06/image006.jpg?w=300 300w, https://github.blog/wp-content/uploads/2026/06/image006.jpg?w=768 768w" sizes="(max-width: 800px) 100vw, 800px" /></figure> <figure class="wp-block-image size-full"><img data-recalc-dims="1" decoding="async" width="800" height="533" src="https://github.blog/wp-content/uploads/2026/06/image007.jpg?resize=800%2C533" alt="A speaker walks among the audience at the workshop." class="wp-image-97121" srcset="https://github.blog/wp-content/uploads/2026/06/image007.jpg?w=800 800w, https://github.blog/wp-content/uploads/2026/06/image007.jpg?w=300 300w, https://github.blog/wp-content/uploads/2026/06/image007.jpg?w=768 768w" sizes="(max-width: 800px) 100vw, 800px" /></figure> <p class="wp-block-paragraph">The OSPORA methodology draws on <a href="https://public.digital/">Public Digital&rsquo;s framework on open source in government</a>, which is a structured set of questions covering not just technical readiness but institutional structures and policies, procurement practices, legal frameworks, and political will.</p> <p class="wp-block-paragraph">What emerged was nuanced. Ghana has political commitment to digitalization, clear champions backed by over a decade of open source delivery experience, and a growing tech community eager to contribute. Importantly, the case for open source is being made from within&mdash;by officials who see it as an essential means to build a more digitally sovereign future, and a closely linked ambition to grow Ghana&rsquo;s national digital economy and local technology capabilities.</p> <p class="wp-block-paragraph">At the same time, there are gaps: the lack of a clear, centralized policy on open source; coordination challenges between the National Information Technology Authority (NITA) and individual ministries operating in siloes; and under-resourced IT teams, especially in rural areas. In instances where progress have stalled, it was rarely attributed to technical reasons&mdash;the more significant barriers are institutional inertia and resistance to change, particularly where existing procurement patterns and vendor relationships are well entrenched.</p> <p class="wp-block-paragraph">These findings are an opportunity to make meaningful improvements and expand the delivery of public services through open source to better serve the people who rely on them.</p> <h2 id="h-a-call-to-action-for-development-impact" class="wp-block-heading">A call to action for development impact</h2> <p class="wp-block-paragraph">Ghana is home to one of West Africa&rsquo;s fastest-growing tech communities, as well as some of the region&rsquo;s first accredited <a href="https://www.unicef.org/innovation/stories/ghana-home-some-west-africas-first-dpgs">Digital Public Goods</a>. It also has the second highest number of GitHub developer accounts in West Africa. With Ghana&rsquo;s <a href="https://onemillioncoders.gov.gh/">One Million Coders</a> initiative underway to skill up a massive developer workforce by 2028 and others digital development initiatives emerging, the foundations are being built, and the talent pipeline is growing.</p> <p class="wp-block-paragraph"><strong>Open source governance shapes what gets built.</strong> Codes that get contributed can support the building of a national infrastructure. For example, UNDP maintains open source projects on GitHub that governments deploy such as the <a href="https://github.com/undp/carbon-registry">National Carbon Registry</a> to help countries implement and manage carbon markets. The policy decisions being made in Ghana and other countries today about data exchange standards, content liability, emerging tech regulation will determine the future of an open source in expanding choices for countries on their digital transformation journeys.</p> <p class="wp-block-paragraph">Ghana&rsquo;s story illustrates what open source sustainability looks like at the national level. While conversations around open source frequently focus on maintainer burnout and funding models, OSPORA represents a different piece of the puzzle on how institutions can sustain open source adoption over time and across administrations at scale. It&rsquo;s also why GitHub alongside UNDP is excited to participate in the United Nations Open Source Week taking place this week in New York City.</p> <p class="wp-block-paragraph">The GitHub team is deeply grateful to the UNDP Digital, AI and Innovation Hub and the Country Office team in Ghana whose vision, persistence, and on-the-ground leadership have made this possible. Partnerships like this don&rsquo;t happen in the abstract, they happen because people show up, do the hard work of building trust across institutions, and stay committed to the long game. This collaboration underscores our shared belief that open source is a powerful catalyst for sustainable development and the growth of digital public goods.</p> <p class="wp-block-paragraph">Open source offers a structurally different path.</p> <div class="wp-block-group post-content-cta has-global-padding is-layout-constrained wp-block-group-is-layout-constrained"> <p class="wp-block-paragraph">Explore <a href="https://github.com/undp">UNDP&rsquo;s open source projects</a>, learn more about <a href="https://www.undp.org/ghana">UNDP Ghana</a>, or check out <a href="https://socialimpact.github.com/">GitHub&rsquo;s Social Impact programs</a>.</p> </div> <p class="wp-block-paragraph"><em>The author is grateful for contributions from Cynthia Lo from GitHub, and Laura Hildebrandt, Benjamin Bertelsen, and Dwayne Carruthers from UNDP.</em></p> </body></html> <p>The post <a href="https://github.blog/open-source/social-impact/github-and-undp-team-up-to-advance-development-priorities-in-ghana-with-open-source/">GitHub and UNDP team up to advance development priorities in Ghana with open source</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Transitioning as a Hubber - The GitHub Blog https://github.blog/?p=97031 2026-06-26T12:00:00.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">When I joined GitHub, my legal name was Ursula&mdash;but my handle was gleeblezoid. Now, as Arthur, I&rsquo;m still gleeblezoid.</p> <p class="wp-block-paragraph">Since our remote-first culture primarily uses handles, transitioning at GitHub was easier than it would have been earlier in my career. I previously worked in IT at companies that only used names for identification, which can be challenging for professionals transitioning.</p> <p class="wp-block-paragraph">I started my career doing IT support and operational work, but being interested in computers meant teaching myself how to code. A colleague from a previous role referred me to GitHub, and I started out five years ago on the IT Engineering team. After repeatedly bothering various security teams with issues and pull requests, I got adopted into the Enterprise Security team after just six months. I&rsquo;ve been there ever since.</p> <p class="wp-block-paragraph">While here, I&rsquo;ve been proud to work with my team on migrating our main SaaS platform to infrastructure as code, and to be a guest speaker a handful of times at Oxford University on the subject of version control.</p> <p class="wp-block-paragraph">But another great thing about working here: GitHub offers gender affirming care related benefits to all employees in terms of covering healthcare; I can expense voice training, HRT prescriptions, and therapy among other things.</p> <p class="wp-block-paragraph">There are also less obvious things that made GitHub a safe place for me to transition. Being a remote-first company means I don&rsquo;t need to agonize over what to wear to the office or who will see me on the way there. Most of my work is captured in writing within Slack or GitHub itself, so when I started voice training and eventually having my voice break on HRT I wasn&rsquo;t spending the whole day talking to people out loud.</p> <p class="wp-block-paragraph">We have the kind of culture where my main avatar can be a cartoon frog in a suit and nobody bats an eye, which removes the entire problem of people guessing my gender by my appearance.</p> <p class="wp-block-paragraph">I know a lot of people in the tech industry, and more trans people than the average person probably does. I know people who are closeted at work, people who go through bureaucratic nightmares on changing their name, and people for whom coming out at work is something they end up doing on a recurring basis with every new set of people they interact with.</p> <p class="wp-block-paragraph">I&rsquo;ve not experienced that. Outside of the understandable bureaucratic friction of changing my name in places like payroll it&rsquo;s been smooth. My team call me what I want to be called and treat me like a regular human being&mdash;as has everyone else at work I&rsquo;ve interacted with. I updated my name and pronouns on our internal systems, and that was that.</p> <p class="wp-block-paragraph">Being trans isn&rsquo;t easy or universally accepted. I&rsquo;m not sure when I&rsquo;ll next get to see my overseas teammates in person, for example. It&rsquo;s also not an experience solely defined by hardship or social barriers. There is a great deal of joy in showing up as yourself and in sharing that joy with others. I nearly cried on a Zoom call when I heard someone use my name and refer to me as &ldquo;him&rdquo; for the first time at work, and I absolutely did cry when one of my teammates sent me a shaving kit in the mail.</p> <p class="wp-block-paragraph">Every Hubber I have mentioned my transition to has been genuinely happy for me. Several of them expressed this through Arthur the aardvark, and Monty Python Holy Grail memes (we are geeks after all).</p> <p class="wp-block-paragraph">I&rsquo;ve always been a man, I just needed time and support to live as one and participate as one in society. In most settings I need to explain to people how Ursula Searle became Arthur Searle, but at GitHub I&rsquo;ve thankfully always been gleeblezoid.</p> </body></html> <p>The post <a href="https://github.blog/developer-skills/career-growth/transitioning-as-a-hubber/">Transitioning as a Hubber</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Evaluating performance and efficiency of the GitHub Copilot agentic harness across models and tasks - The GitHub Blog https://github.blog/?p=97041 2026-06-25T22:59:45.000Z <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd"> <html><body><p class="wp-block-paragraph">While the model provides&#8239;the raw&#8239;intelligence, the harness shapes how effectively that intelligence is applied. The GitHub Copilot agentic harness is a single shared component of the <a href="https://github.com/github/copilot-sdk?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot SDK</a>, which powers the <a href="https://github.com/features/copilot/cli?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot CLI</a>, <a href="https://github.com/features/ai/github-app?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot app</a>, and <a href="https://docs.github.com/copilot/concepts/agents/code-review?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">Copilot code review</a>, along with a wide variety of experiences across GitHub and Microsoft. Improve the harness, and every surface benefits.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" fetchpriority="high" decoding="async" height="592" width="1024" src="https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?resize=1024%2C592" alt="Diagram showing the agentic harness powers the GitHub Copilot CLI, the GitHub Copilot app, other IDEs like VS Code and Xcode, and others built with the SDK." class="wp-image-97049" srcset="https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=3000 3000w, https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/architecture-harness-surfaces.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>The GitHub Copilot&nbsp;agentic&nbsp;harness powers&nbsp;GitHub Copilot experiences.</em></figcaption></figure> <p class="wp-block-paragraph">The tools, context, and workflow are orchestrated by the harness. A harness should be fast, token-efficient, and predictable for developers. That&rsquo;s what we designed GitHub Copilot&rsquo;s agentic harness to do.</p> <p class="wp-block-paragraph">In this post, we&rsquo;ll present data showing the efficiency and performance of the GitHub Copilot agentic harness across a wide range of agentic software engineering tasks.</p> <aside data-color-mode="light" data-dark-theme="dark" data-light-theme="light_dimmed" class="wp-block-group post-aside--large p-4 p-md-6 is-style-light-dimmed has-global-padding is-layout-constrained wp-block-group-is-layout-constrained is-style-light-dimmed--1" style="border-top-width:4px"> <h2 id="h-more-optimizations-we-are-making" class="wp-block-heading h5-mktg gh-aside-title is-typography-preset-h5" style="margin-top:0">More optimizations we are making</h2> <p class="wp-block-paragraph">Read more about <a href="https://github.blog/ai-and-ml/github-copilot/getting-more-from-each-token-how-copilot-improves-context-handling-and-model-routing/">our latest optimizations on context handling and model routing to get the most out of each token</a>. We have also shared more <a href="https://github.blog/ai-and-ml/how-we-made-github-copilot-cli-more-selective-about-delegation/">about experiments and optimizations around delegation</a>, and how it benefits developers today.</p> </aside> <h2 id="h-how-we-iterate-with-benchmarks" class="wp-block-heading">How we iterate with benchmarks</h2> <p class="wp-block-paragraph">We&#8239;continuously&#8239;evaluate the capability and efficiency of&#8239;the GitHub Copilot agentic harness through a combination of public and internally developed benchmarks. Our public benchmarks include industry standards, while several internal benchmarks are derived from large codebases inside GitHub and Microsoft. We complement this with real-world metrics and online experiments to ensure we understand the harness&rsquo;s performance in controlled environments and its practical&#8239;impact on&#8239;agentic problem solving and task completion.&#8239;</p> <p class="wp-block-paragraph">We control as many variables as possible to evaluate the performance of GitHub Copilot&rsquo;s harness compared to the model provider&rsquo;s harness: use the <strong>same model</strong>, the <strong>same benchmark task</strong>, normalized on context window, reasoning efforts, tool selection, and MCP servers.</p> <p class="wp-block-paragraph">Below we report our latest results for a subset of the benchmarks we track, across four leading models: <strong>Claude Sonnet 4.6</strong>, <strong>Claude Opus 4.7</strong>, <strong>GPT&#8209;5.4</strong>, and <strong>GPT&#8209;5.5</strong>:</p> <figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><th><strong>Benchmark</strong>&nbsp;</th><th><strong>Domain</strong>&nbsp;</th><th><strong>Purpose</strong>&nbsp;</th></tr></thead><tbody><tr><td>SWE-bench Verified&nbsp;</td><td>500 human-validated bug-fix tasks from open-source Python repositories&nbsp;</td><td>Established industry-standard benchmark for coding agents&nbsp;</td></tr><tr><td>SWE-bench Pro&nbsp;</td><td>More difficult, multi-step engineering tasks requiring deeper reasoning and broader code changes&nbsp;</td><td>Better reflects complex, real-world software engineering work&nbsp;</td></tr><tr><td>SkillsBench&nbsp;</td><td>How effectively an agent uses skills to solve tasks&nbsp;</td><td>Evaluates extensibility and&nbsp;skill&nbsp;use&nbsp;and triggering&nbsp;capabilities&nbsp;</td></tr><tr><td>TerminalBench&nbsp;</td><td>Agent performance on terminal-based tasks&nbsp;</td><td>Measures effectiveness in command-line workflows used by developers&nbsp;</td></tr><tr><td>Win-Hill&nbsp;</td><td>Internal benchmark for tasks running inside Windows containers&nbsp;</td><td>Validates that performance generalizes across operating systems and environments&nbsp;</td></tr></tbody></table></figure> <p class="wp-block-paragraph">Throughout, we compare <strong>GitHub</strong> <strong>Copilot CLI</strong> against the model-vendor harnesses that ship those models natively: <strong>Claude Code</strong> for Sonnet 4.6 and Opus 4.7, and <strong>Codex CLI</strong> for GPT&#8209;5.4 and GPT&#8209;5.5.</p> <h2 id="h-token-efficiency" class="wp-block-heading">Token efficiency</h2> <p class="wp-block-paragraph">Holding the model and task fixed, across multiple benchmark results, the GitHub Copilot harness achieves task completion rates on par with other model-vendor harnesses, while showing lower token consumption across most configurations.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="683" width="1024" src="https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?resize=1024%2C683" alt="Chart showing Copilot CLI versus model-vendor harnesses using SWE-bench Verified, SWE-bench Pro, SkillsBench, Win-Hill, and TerminalBench2 tests. For Sonnet 4.6 and Opus 4.7, Copilot CLI performed better in all cases, using fewer tokens. For GPT 5.4 and GPT 5.5, CLI performed better in all cases except SWE-bench Verified, where it did 7% and 4% worse, respectively." class="wp-image-97050" srcset="https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=3000 3000w, https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/token-efficiency.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>Token efficiency:&nbsp;GitHub&nbsp;Copilot CLI vs.&#8239;other&nbsp;model-vendor&nbsp;harnesses</em></figcaption></figure> <h2 id="h-task-resolution" class="wp-block-heading">Task resolution</h2> <p class="wp-block-paragraph">Token efficiency only matters if the work actually gets done.</p> <p class="wp-block-paragraph">Task resolution rates&#8239;for the GitHub Copilot&#8239;agentic harness across&#8239;these benchmarks&#8239;are&#8239;on-par with model-vendor harnesses when used with a fixed model and benchmark task.&#8239;This ensures that the full potential of the underlying model is available, along with multi-model flexibility,&#8239;token efficiency, and memory and context capabilities.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" decoding="async" height="786" width="1024" src="https://github.blog/wp-content/uploads/2026/06/task-resolution.png?resize=1024%2C786" alt="Task resolution benchmarking test results for Copilot CLI versus model-vendor harnesses. For SWE-bench Verified tests, Copilot CLI performed better with Sonnet 4.6 and Opus 4.7, but worse with GPT 5.4 and GPT 5.5. For SWE-bench Pro, Copilot CLI only performed slightly worse with Sonnet 4.6, and performed better for other models. For SkillsBench, Copilot CLI performed worse for Sonnet 4.6 and Opus 4.7, but better for GPT models. For Win-Hill, Copilot CLI performed equal or better for all models. For TerminalBench 2, Copilot CLI performed better for Sonnet 4.6 and Opus 4.7, equal for GPT 5.5, and worse for GPT 5.4." class="wp-image-97051" srcset="https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=3000 3000w, https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/task-resolution.png?w=2048 2048w" sizes="(max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>Task resolution:&nbsp;GitHub&nbsp;Copilot CLI vs.&#8239;the&nbsp;</em>model-vendor harnesses</figcaption></figure> <p class="wp-block-paragraph">These results reflect effective parity, since the differences in either direction are within the variance due to the stochastic nature of the models, making the cross-harness performance on-par.</p> <h2 id="h-terminalbench-token-efficiency-task-completion-and-variance" class="wp-block-heading">TerminalBench: Token efficiency, task completion, and variance</h2> <p class="wp-block-paragraph">To continuously improve the GitHub Copilot agentic harness on task completion and token efficiency, we regularly perform thorough analyses across benchmarks. Below is an example of variance analysis on TerminalBench 2.0, which not only highlights GitHub Copilot&rsquo;s strength on task completion and token efficiency, but also shows the run-to-run variance intrinsic to this kind of benchmark.</p> <figure class="wp-block-image size-large"><img data-recalc-dims="1" loading="lazy" decoding="async" height="676" width="1024" src="https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?resize=1024%2C676" alt="A diagram showing mean cost per task compared to the resolution rate. Copilot CLI performs equal to or better than model-vendor harnesses." class="wp-image-97060" srcset="https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=3000 3000w, https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=300 300w, https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=768 768w, https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=1024 1024w, https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=1536 1536w, https://github.blog/wp-content/uploads/2026/06/resolution-rate-vs-cost-variance-noarrow.png?w=2048 2048w" sizes="auto, (max-width: 1000px) 100vw, 1000px" /><figcaption class="wp-element-caption"><em>Resolution rate vs.&#8239;cost per task.&nbsp;Up and to the left is better: solve more, spend less.&nbsp;</em></figcaption></figure> <p class="wp-block-paragraph">Every marker is one agent-and-model configuration on TerminalBench 2.0, with resolution rate on the vertical axis and dollar cost per task on the horizontal axis. The shaded ellipse around each point shows the &plusmn;1&sigma; run-to-run spread, displaying how much each configuration varies between runs.</p> <p class="wp-block-paragraph">Three things stand out:</p> <ol class="wp-block-list"> <li><strong>GitHub Copilot&rsquo;s agentic harness is on par with or ahead of other agents on task completion and cost per task across the configurations we evaluated</strong>. Purple (Copilot) markers and their same-model competitors sit within overlapping ellipses on both axes for nearly every model&mdash;the differences are inside run-to-run variance. Copilot is never below a competitor on completion or to the right on cost.</li> <li><strong>Run-to-run variability</strong>. We ran each agent-model combination at least five times. The ellipse marks the 1&sigma; spread of those runs; a tighter ellipse in the chart means more reproducible results, while a wider one shows results swinging further from run to run on both cost and task completion.</li> <li><strong>The benefit of GitHub Copilot&rsquo;s model choice:</strong> The chart shows a real trade-off: GPT models (left) deliver the best value: strong resolution at the lowest cost. Claude Opus (upper right) reaches the highest resolution at a premium. GitHub Copilot puts both on the table, so you can pick efficiency or peak quality per task.</li> </ol> <h2 id="h-one-harness-many-models" class="wp-block-heading">One harness, many models</h2> <p class="wp-block-paragraph">The GitHub Copilot agentic harness supports <strong>20+ frontier models</strong> across the GPT, Claude, Gemini, and MAI families, plus bring your own key for open&#8209;source and local models. You can choose the right model for the capability and cost profile of each task, or let <a href="https://docs.github.com/en/copilot/concepts/models/auto-model-selection"><strong>Auto model selection</strong></a> choose for you, balancing task intent and model health to optimize token efficiency.</p> <p class="wp-block-paragraph">A multi&#8209;model architecture also unlocks harness&#8209;level capabilities a model-vendor harness simply can&rsquo;t offer. <a href="https://github.blog/ai-and-ml/github-copilot/github-copilot-cli-combines-model-families-for-a-second-opinion/"><strong>Rubber Duck</strong></a>, for example, uses cross&#8209;model&#8209;family critique, where one model reviews another&rsquo;s work to improve outcomes beyond what any single model produces alone.</p> <h2 id="h-conclusion" class="wp-block-heading">Conclusion</h2> <p class="wp-block-paragraph">Benchmarks are just one signal among several. We are constantly working to improve quality across benchmarks, real-world usage metrics, and online experiments, while pushing to efficiently make the most out of every token.</p> <p class="wp-block-paragraph">GitHub Copilot delivers task&#8209;resolution on par with leading model-vendor harnesses while using fewer tokens across several configurations, without locking you into a single model through its multi&#8209;model architecture. For developers, this means you can get comparable task completion with lower token cost, while still choosing the model that best fits your task.</p> <h2 id="h-try-it-yourself" class="wp-block-heading">Try it yourself</h2> <p class="wp-block-paragraph">Try GitHub Copilot with the model of your choice, compare approaches on the tasks you run every day, and see how different models and agent strategies perform in your environment.</p> <p class="wp-block-paragraph">Learn more about:</p> <ul class="wp-block-list"> <li><a href="https://github.com/features/copilot/cli?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot CLI</a></li> <li><a href="https://github.com/features/ai/github-app?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot app</a></li> <li><a href="https://github.com/github/copilot-sdk?utm_source=blog-benchmarking-1&amp;utm_medium=blog&amp;utm_campaign=github-copilot-app-ga-2026" target="_blank" rel="noreferrer noopener">GitHub Copilot SDK</a></li> </ul> <p class="wp-block-paragraph">The same agentic harness powers these experience. We&rsquo;re continuing to improve its quality, efficiency, and flexibility.</p> <h2 id="h-methodology" class="wp-block-heading">Methodology</h2> <p class="wp-block-paragraph">To make the comparison as controlled and reproducible as possible, we run each agent with equivalent settings across models, tasks, and environments.</p> <p class="wp-block-paragraph">All runs have a two-hour timeout. All agents run non-interactively single-turn, with web-tools disabled, and all tools allowed.</p> <p class="wp-block-paragraph"><strong>TerminalBench2 analysis</strong>: Default settings enabled for agents with reasoning effort set to medium (e.g. tool search is enabled for Claude Code and Copilot CLI uses github-mcp-server). Codex and Claude Code use direct Anthropic and OpenAI endpoints. To ensure complete and reliable results, any missing data or infrastructure-related failures were re-run until all 89 TerminalBench2 tasks produced results. Model-generated errors were retained and not excluded from the analysis. Each model was evaluated across five independent runs, and Copilot was tested in two separate evaluation batches to enable comparison with Claude Code and Codex.</p> <p class="wp-block-paragraph"><strong>All benchmarks</strong>: All agent model pairs normalized to same context window size, same prompt token limits, reasoning effort (medium) and settings&mdash;no tool search, no MCP servers. Keeping the harness&rsquo;s default built-in tools. Infrastructure-related anomalies and network-access effects are excluded across all agents for a benchmark to ensure fair comparisons. To reduce the impact of run-to-run variability on smaller benchmarks (&lt;100 instances), five independent runs were conducted, and the best scored run is reported. All metrics are presented as pass@1. These normalizations mean results differ from public benchmark submissions, which typically use higher reasoning effort and other tuned settings.</p> </body></html> <p>The post <a href="https://github.blog/ai-and-ml/github-copilot/evaluating-performance-and-efficiency-of-the-github-copilot-agentic-harness-across-models-and-tasks/">Evaluating performance and efficiency of the GitHub Copilot agentic harness across models and tasks</a> appeared first on <a href="https://github.blog">The GitHub Blog</a>.</p> Privacy-Aware Infrastructure in the AI-Native Era: An Asset Classification Case Study - Engineering at Meta https://engineering.fb.com/?p=24095 2026-06-25T22:30:51.000Z <p><span style="font-weight: 400;"> Privacy controls — systems that enforce retention, access, allowed-purpose, downstream-sharing, or anonymization policies — require a reliable understanding of data to function. Before such a control can operate effectively, it must know exactly what it is looking at. This can be complex, as demonstrated by a field simply named &#8220;</span><span style="font-weight: 400; font-family: 'courier new', courier;">age</span><span style="font-weight: 400;">&#8220;: In one context, it might describe a person and require strict protections, while in another, it could be a cache time-to-live (TTL) numerical value in an infrastructure pipeline.</span></p> <figure id="attachment_24098" aria-describedby="caption-attachment-24098" style="width: 1996px" class="wp-caption alignnone"><img fetchpriority="high" decoding="async" class="wp-image-24098 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png" alt="" width="1996" height="1163" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png 1996w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=916,534 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=768,447 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=1024,597 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=1536,895 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=96,56 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-1-e1782178036492.png?resize=192,112 192w" sizes="(max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24098" class="wp-caption-text">Figure 1: One column name, two governance outcomes. The identical field <span style="font-family: 'courier new', courier;">age</span> is <strong>personal data</strong> when it describes a person, but ordinary <strong>system metadata</strong> when it is a cache TTL. Which is why a name alone cannot determine the privacy requirement.</figcaption></figure> <p><span style="font-weight: 400;">This is the everyday problem behind </span><a href="https://engineering.fb.com/2025/10/23/security/scaling-privacy-infrastructure-for-genai-product-innovation/" target="_blank" rel="noopener"><span style="font-weight: 400;">privacy-aware infrastructure (PAI)</span></a><span style="font-weight: 400;">: The inputs are noisy and probabilistic, but the outputs need to be precise enough to drive enforcement. </span></p> <p><span style="font-weight: 400;">AI-native products make that problem harder. They introduce new data modalities, faster iteration cycles, derived features, embeddings, multimodal inputs, and changing policy interpretations. Manual review remains important for judgment and accountability, but it cannot keep up with the volume and pace of change.</span></p> <p><span style="font-weight: 400;">At Meta, we apply a hybrid pattern for asset classification at scale:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Build a rich context before asking a model to reason.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Use LLMs to handle ambiguity, cold start, and novelty.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Keep human-reviewed labels separate from model-generated recommendations.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Distill stable behavior into deterministic, versioned rules for routine enforcement.</span></li> </ul> <p><span style="font-weight: 400;">The end goal is not &#8220;LLMs everywhere.&#8221; Instead, it is a system that can learn from ambiguous signals while moving production enforcement toward logic that is low latency, replayable, and easier to audit.</span></p> <p><span style="font-weight: 400;">The LLM does not make the production decision in the common case, deterministic rules do. We use LLMs deliberately and narrowly, to interpret novel or ambiguous assets, and then to distill what they learn into versioned human-reviewed deterministic rules, which steadily shrinks the LLM&#8217;s role in production over time. Humans stay in the loop where it matters most. People adjudicate the reviewed reference labels, and they review and approve rule promotions that could change how protection is enforced.</span></p> <p><span style="font-weight: 400;">PAI addresses four operational concerns: </span></p> <ol> <li style="font-weight: 400;" aria-level="1"><b>Understand</b><span style="font-weight: 400;"> what data exists and how it is governed. </span></li> <li style="font-weight: 400;" aria-level="1"><b>Discover</b><span style="font-weight: 400;"> which data flows are relevant to a policy question. </span></li> <li style="font-weight: 400;" aria-level="1"><b>Enforce</b><span style="font-weight: 400;"> retention/access/purpose/sharing constraints.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Demonstrate</b><span style="font-weight: 400;"> compliance through verifiable evidence.</span></li> </ol> <p><span style="font-weight: 400;">Asset classification sits at the </span><b>understand</b><span style="font-weight: 400;"> layer. It provides the foundation that every downstream concern depends on.</span></p> <figure id="attachment_24099" aria-describedby="caption-attachment-24099" style="width: 1996px" class="wp-caption alignnone"><img decoding="async" class="wp-image-24099 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png" alt="" width="1996" height="1303" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png 1996w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=916,598 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=768,501 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=1024,668 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=1536,1003 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=96,63 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-2-e1782178335368.png?resize=192,125 192w" sizes="(max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24099" class="wp-caption-text">Figure 2: The privacy-aware infrastructure stack is a dependency pyramid: each capability rests on the one below it. <strong>Understand</strong> —classifying what the data actually is — is the load-bearing base. If it is wrong, everything above (discover, enforce, demonstrate) inherits the error.</figcaption></figure> <h2><span style="font-weight: 400;">Why Asset Classification Matters</span></h2> <p><span style="font-weight: 400;">Asset classification is the foundation for many privacy controls. Before a system can enforce retention, access, allowed-purpose, downstream-sharing, or anonymization policies, it needs a reliable view of what the asset is and how it should be governed.</span></p> <p><span style="font-weight: 400;">An asset can be more than a table or column. It can be a nested field inside a payload, a log key, an event parameter, an API field, a machine learning (ML) feature, an embedding, or a derived dataset produced by an intermediate pipeline. That breadth matters because AI-native systems often transform data across many representations. A single source signal can move through pipelines, become a feature, appear in a model-training workflow, or be joined with other derived signals. Classification has to follow the meaning of the data, not just its shape.</span></p> <p><span style="font-weight: 400;">There are four recurring challenges:</span></p> <p><b>First, noisy and weak signals</b><span style="font-weight: 400;">: Dozens of context fields are fetched per asset, which forces the model to rediscover what matters each time. High token usage dilutes attention, and decision boundaries get buried in irrelevant or misleading fields. A field called </span><span style="font-weight: 400;">age</span><span style="font-weight: 400;"> in a caching pipeline is a concrete example: Without code resolution and lineage analysis, a classifier will trigger false restrictions on the entire pipeline.</span></p> <p><b>Second, the relevant context is distributed</b><span style="font-weight: 400;">. Code, lineage, ownership, semantic annotations, documentation, and usage patterns often live in different systems. A good classifier needs to assemble that context before making a decision.</span></p> <p><b>Third, requirements evolve</b><span style="font-weight: 400;">. Product teams move quickly, and policy interpretation can change as new product capabilities appear. A static rule set or periodic manual review process can leave gaps between reviews.</span></p> <p><b>Fourth, classification is only useful if it feeds enforcement</b><span style="font-weight: 400;">. A false positive can trigger unnecessary restrictions downstream. A false negative can leave a protection gap. The classifier sits near the front of the enforcement pipeline, so its error profile affects every system that depends on it.</span></p> <p><span style="font-weight: 400;">This creates the central tension: Classification needs to reason under ambiguity, but enforcement needs decisions that can be explained and reproduced later.</span></p> <figure id="attachment_24100" aria-describedby="caption-attachment-24100" style="width: 1996px" class="wp-caption alignnone"><img decoding="async" class="wp-image-24100 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png" alt="" width="1996" height="880" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png 1996w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=916,404 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=768,339 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=1024,451 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=1536,677 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=96,42 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-3-e1782178426532.png?resize=192,85 192w" sizes="(max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24100" class="wp-caption-text">Figure 3: Four distinct difficulties (context dependence, sparse signal, a heavy long tail, and constant schema drift) all collapse into a single tension: Classification wants to <strong>reason under ambiguity</strong>, while enforcement demands results it can <strong>explain and reproduce</strong>. The whole design exists to hold these two in balance.</figcaption></figure> <h2><span style="font-weight: 400;">The Pattern</span></h2> <p><span style="font-weight: 400;">Our approach is built around three principles that emerged from building and operating the system:</span></p> <p><b>First, context beats prompts.</b><span style="font-weight: 400;"> Most classification failures were not caused by weak instructions; they were caused by weak or missing evidence. Hours of prompt optimization produced marginal improvement when the model was reasoning over raw, noisy fields. </span><i><span style="font-weight: 400;">Structuring context into evidence briefs, with supporting signals, contradicting signals, provenance, and masked circular fields, produced much larger accuracy improvements.</span></i><span style="font-weight: 400;"> The practical lesson is simple: Focus on what goes into the model before optimizing how you ask.</span></p> <p><b>Second, decouple evaluation from optimization.</b><span style="font-weight: 400;"> LLM outputs are useful recommendations, but they cannot become their own ground truth. </span><i><span style="font-weight: 400;">The evaluation loop needs to stay independent from the classifier</span></i><span style="font-weight: 400;">: different models, different prompt strategies, frozen reference sets, human-reviewed labels, and regression gates. If evaluation and optimization share the same loop, the system can end up measuring drift instead of progress.</span></p> <p><b>Third, distill stable behavior into deterministic rules.</b><span style="font-weight: 400;"> LLMs are useful for ambiguity, cold start, and new patterns. They are not the right default enforcement mechanism at scale. </span><i><span style="font-weight: 400;">When the system finds stable, validated patterns, those patterns should become versioned, auditable rules that run without the LLM</span></i><span style="font-weight: 400;">. Over time, the classifier should progressively shrink its own LLM surface area, leaving model inference for novel or ambiguous assets while routine enforcement becomes deterministic, low-latency, and replayable.</span></p> <p><span style="font-weight: 400;">These principles translate into a concrete operating pattern: Define a stable classification contract, build a context mesh, route decisions through a deterministic-first funnel, and keep the learning loop safe with independent evaluation and reviewed labels.</span></p> <p><span style="font-weight: 400;">To execute on this pattern, we break the work down into seven practical stages. These stages transform the high-level architecture into a concrete, repeatable process.</span></p> <p><span style="font-weight: 400;">The rest of this post walks through those pieces using asset classification as the case study.</span></p> <figure id="attachment_24101" aria-describedby="caption-attachment-24101" style="width: 1999px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-24101" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png" alt="" width="1999" height="1358" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png 1999w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=916,622 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=768,522 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=1024,696 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=1536,1043 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=96,65 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-4.png?resize=192,130 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24101" class="wp-caption-text"><span style="font-weight: 400;">Figure 4: The two-lane operating pattern: </span><b>(1)</b><span style="font-weight: 400;"> Most requests (~85%) resolve on the deterministic path in single-digit milliseconds, and within ~40 ms including context assembly; the ~15% LLM fallback is slower (seconds) and budgeted separately; </span><b>(2-3)</b><span style="font-weight: 400;"> a nightly offline lane samples served decisions, adjudicates them against reviewed truth, and re-evaluates; </span><b>(4)</b><span style="font-weight: 400;"> distilled rules are promoted back into the live decision funnel by content-addressed swap. The masking invariant holds on both lanes.</span></figcaption></figure> <h3><span style="font-weight: 400;">1.) Start With the Contract</span></h3> <p><span style="font-weight: 400;">A classifier should behave like a platform service. That means its contract should be small, explicit, and stable. For each asset, the classifier receives an identifier and a bundle of context. It returns a structured result with:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A category from the classifier&#8217;s taxonomy.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A confidence score – a raw model self-assessment whose calibration we evaluate against reviewed labels (see below).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">A decision trace showing which evidence influenced the result.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">The rule that matched, if the decision came from deterministic logic.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Version information for the context, rules, and prompt used to make the decision.</span></li> </ul> <p><span style="font-weight: 400;">The taxonomy is domain-specific. One classifier might distinguish user data from operational data. Another might classify whether an asset is eligible for a particular AI-training use case. We avoid forcing every classifier into one universal taxonomy. Instead, each classifier owns one scoped question, and downstream systems compose the answers when they need multiple facets.</span></p> <p><span style="font-weight: 400;">That scoping is important. A narrow classifier is easier to evaluate, easier to debug, and easier to govern. It also makes the decision trace more meaningful because the classifier is explaining one decision, not trying to solve every policy question at once.</span></p> <figure id="attachment_24102" aria-describedby="caption-attachment-24102" style="width: 1999px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="wp-image-24102 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png" alt="" width="1999" height="900" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png 1999w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=916,412 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=768,346 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=1024,461 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=1536,692 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=96,43 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-5-e1782178564887.png?resize=192,86 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24102" class="wp-caption-text">Figure 5.:The classifier is a <strong>service contract</strong>, not a prompt: a fixed request in, a typed result out. Three response fields — <span style="font-family: 'courier new', courier;">matched_rule</span>, <span style="font-family: 'courier new', courier;">decision_trace</span>, and <span style="font-family: 'courier new', courier;">versions</span> — are what make every classification replayable and auditable after the fact.</figcaption></figure> <h3><span style="font-weight: 400;">2.) Build Context Before Prompting</span></h3> <p><span style="font-weight: 400;">Most classification failures are not prompt failures. They are context failures. If the only signal is a field name, the model has to guess. If the system can also provide code references, lineage, ownership, semantic annotations, and nearby usage, the model can reason from better evidence.</span></p> <p><span style="font-weight: 400;">In practice, the context mesh may include:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Source-code resolution, including where a field is defined or used.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Ownership and organizational metadata.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Semantic annotations, such as data type or origin.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Lineage signals that show where data came from and where it flows.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">ML heuristic outputs from scanners or embedding-based classifiers.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Code search results that show references, logging declarations, or call sites.</span></li> </ul> <p><span style="font-weight: 400;">The point is not to pass everything to the LLM. More context is not automatically better. Some fields are redundant. Some are noisy. Some can create circular reasoning if they already encode the label we are trying to predict.</span></p> <p><span style="font-weight: 400;">So the system creates an </span><b>evidence brief – </b><span style="font-weight: 400;">a compact summary of the strongest supporting signals, contradicting signals, and provenance chains. Instead of asking the model to sift through raw context, we ask it to reason over the evidence that is most relevant to the classification decision.</span></p> <figure id="attachment_24103" aria-describedby="caption-attachment-24103" style="width: 1426px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-24103" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png" alt="" width="1426" height="850" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png 1426w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png?resize=916,546 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png?resize=768,458 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png?resize=1024,610 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png?resize=96,57 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-6.png?resize=192,114 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24103" class="wp-caption-text">Figure 6: The evidence brief assembled for one asset. Each signal is weighted by reliability (bar length) and split into <strong>support</strong> versus <strong>contra</strong>. The pre-existing privacy label is deliberately <strong>masked</strong>. Feeding it back would let the model grade its own homework and collapse the signal.</figcaption></figure> <p><span style="font-weight: 400;">Without this structuring, the model receives dozens of raw fields per asset and must rediscover what matters leading to high token consumption, diluted attention, and decision boundaries buried in noise. The evidence brief solves this by pre-ranking signals. For a field like </span><span style="font-weight: 400; font-family: 'courier new', courier;">user_payload.email_address</span><span style="font-weight: 400;">, an evidence brief might say:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Supporting signal: Lineage connects the asset to a user-facing logging pipeline (weight 0.8).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Supporting signal: Semantic annotation indicates EMAIL-like data (weight 0.9).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Contradicting signal: Ownership metadata points to an infrastructure team, not a user-facing product (weight 0.3).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Suppressed signal: An existing privacy label was removed to avoid circular reasoning.</span></li> </ul> <p><span style="font-weight: 400;">That last point matters. A model should not be allowed to &#8220;discover&#8221; the correct answer by reading a field that already contains the answer. Masking is not just prompt hygiene, it is a system invariant. Fields masked from the LLM are also blocked from learned rule distillation so the model cannot smuggle the answer into a rule by way of a circular field. Deterministic rules that use high-risk fields require explicit review.</span></p> <p><span style="font-weight: 400;">Over time, the system can also learn which context fields are useful. Fields that consistently improve classification can be prioritized. Fields that are unstable, redundant, or harmful can be suppressed. This turns signal quality from a matter of intuition into something measurable.</span></p> <h3><span style="font-weight: 400;">3.) Use a Decision Funnel</span></h3> <p><span style="font-weight: 400;">Once the context is assembled, the classifier routes the asset through a decision funnel.</span></p> <p><span style="font-weight: 400;">The first path is </span><b>deterministic</b><span style="font-weight: 400;">. If a known, versioned rule matches the asset, the classifier can return a decision quickly and with a clear explanation. Deterministic rules work well for stable patterns – a well-understood namespace, a semantic annotation with high precision, or a combination of signals that has been validated over time.</span></p> <p><span style="font-weight: 400;">The second path is </span><b>LLM-based</b><span style="font-weight: 400;">. If the asset is novel, ambiguous, or outside current rule coverage, the classifier asks the model to reason over the evidence brief. The model returns a candidate label, confidence indicators, a decision path, and cited evidence.</span><span style="font-weight: 400;"><br /> </span><span style="font-weight: 400;"><br /> </span><span style="font-weight: 400;">In our production deployment, Figure 7 shows how cheap deterministic rules resolve the large majority of traffic, roughly 85%, in single-digit milliseconds. The LLM is reserved as a fallback for the roughly 15% that is novel or ambiguous. That path is slower — on the order of seconds — and roughly 400 times the compute cost, so it is budgeted separately. Both paths emit the identical result schema. The masking invariant is enforced on each.</span></p> <figure id="attachment_24104" aria-describedby="caption-attachment-24104" style="width: 1996px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="wp-image-24104 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png" alt="" width="1996" height="986" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png 1996w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=916,452 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=768,379 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=1024,506 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=1536,759 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=96,47 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-7-e1782178654322.png?resize=192,95 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24104" class="wp-caption-text"><span style="font-weight: 400;">Figure 7: Cheap, deterministic rules resolve the large majority of traffic (~85%) in single-digit milliseconds; the LLM is reserved as a fallback for the ~15% that is novel or ambiguous, a path that is slower (on the order of seconds) and roughly 400 times the compute cost, budgeted separately. Both paths emit the identical result schema, and the masking invariant is enforced on each.</span></figcaption></figure> <p><span style="font-weight: 400;">That confidence deserves a careful read. The raw score is a model self-assessment, a number the model produces from its own judgment, not an inherent probability of being correct. So we evaluate its calibration against reviewed labels. Raw scores are compared to the correctness rate actually observed on the human-reviewed reference set, which tells us how well a given score tracks a real probability of being right. Confidence-based routing in the funnel, for example, accept automatically versus route to human review, should use calibrated scores where that calibrated path is enabled, rather than the raw number</span></p> <p><span style="font-weight: 400;">Both paths emit the same result format. Downstream enforcement systems do not need to know whether a decision came from a rule or from model-based reasoning. They receive a category, confidence, trace, and versioned decision metadata.</span></p> <p><span style="font-weight: 400;">This split is what makes the pattern practical. LLMs are useful for ambiguity and cold start. Rules are better for routine enforcement. The more stable behavior we can distill into rules, the less often the serving path needs model inference.</span></p> <p><span style="font-weight: 400;">Rule coverage becomes an important operational metric. If coverage rises while quality holds steady, the classifier is moving toward a healthier steady state: fewer routine calls to the model, lower resource use, lower latency, and decisions that are easier to replay.</span></p> <p><span style="font-weight: 400;">A critical system invariant: Fields masked from the LLM are also blocked from learned rule distillation, so a masked signal cannot re-enter the decision through an automatically distilled rule. In one production deployment, a subtle bug in how masked context was handled during rule evaluation caused rules to silently fall through to LLM fallback, so rule coverage appeared to plateau even as the rule set grew. Fixing that handling immediately increased rule coverage and cut LLM inference calls significantly. </span></p> <p><span style="font-weight: 400;">The lesson: Masking is not a prompt-engineering concern, it is a system invariant. And deterministic rules that rely on high-risk fields require explicit review rather than inheriting masking implicitly.</span></p> <h3><span style="font-weight: 400;">4.) Solve Cold Start Deliberately</span></h3> <p><span style="font-weight: 400;">On day zero, a classifier has a hard problem: There may be millions of assets and very few reviewed labels. Random sampling is not enough. The categories that matter most for privacy can be rare, and rare categories are easy to miss if you wait for examples to appear naturally.</span></p> <p><span style="font-weight: 400;">Instead, we seed the process with policy-guided examples:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Rare sensitive categories.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Borderline cases where policy interpretation is difficult.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Negative examples that look sensitive but are not.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Assets where context signals disagree.</span></li> </ul> <p><span style="font-weight: 400;">The goal is not to eliminate human review. It is to focus human attention on the cases where judgment matters most.</span></p> <h3><span style="font-weight: 400;">5.) Keep the Learning Loop Safe</span></h3> <p><span style="font-weight: 400;">Once the classifier is live, it needs to improve without grading its own homework.</span></p> <p><span style="font-weight: 400;">We separate two loops:</span></p> <p><span style="font-weight: 400;">The </span><b>reference loop</b><span style="font-weight: 400;"> produces reviewed labels. These labels are append-only, versioned, and tracked with provenance. If a label changes, the history is preserved rather than overwritten. Model-generated labels are useful recommendations, but they do not become reference labels automatically. Humans adjudicate uncertain or high-risk cases, and those adjudicated labels become the reference set for evaluation.</span></p> <p><span style="font-weight: 400;">The </span><b>optimization loop</b><span style="font-weight: 400;"> improves prompts, routing, context usage, and candidate rules. It can evolve quickly, but it is evaluated against the reviewed reference set, not against labels produced by the same model it is trying to optimize. This distinction matters: A classifier that trains or validates itself on its own predictions can appear to improve while drifting away from the policy intent.</span></p> <p><span style="font-weight: 400;">For quality control, we use a </span><b>multi-panel judge – </b><span style="font-weight: 400;">three independent LLM evaluations, each with a different prompt strategy. One classifies directly from evidence. One critiques the reasoning first, then classifies. One focuses exclusively on metadata signals, such as on-call, lineage, and semantic annotations, while ignoring names and descriptions. All three share a single judge model, a larger reasoning model deliberately different from the classifier model.</span></p> <p><span style="font-weight: 400;">The three judges share one scaffold and differ only in how they are asked to reason. The skeleton below is illustrative, not the literal production prompts, but it shows the structure. Each judge receives the same masked evidence brief, the masking invariant still holds, and each returns a structured verdict.</span></p> <pre class="line-numbers"><code class="language-none"># Shared scaffold (all three judges) INPUT = masked_evidence_brief # pre-existing privacy label removed; masking invariant holds OUTPUT = {label, rationale, confidence} JUDGE_MODEL = larger reasoning model, deliberately != classifier model # V1 - direct-from-evidence verdict_1 = judge(brief, instruction="Classify the asset directly from the evidence.") # V2 - critique-then-classify verdict_2 = judge(brief, instruction="First critique the supporting and contradicting signals, then classify.") # V3 - metadata-only verdict_3 = judge(brief, instruction="Use ONLY metadata signals (on-call, lineage, semantic annotations). Ignore names and descriptions.") # Aggregate final_label = majority_vote(verdict_1, verdict_2, verdict_3) Agreement = cohens_kappa(verdict_1, verdict_2, verdict_3) # inter-rater reliability </code></pre> <p><span style="font-weight: 400;">Results aggregate by majority vote. We track panel agreement across the three judge framings as a stability signal, while Cohen&#8217;s kappa (κ) compares the judge consensus against the reference labels (or against the classifier output), providing a statistical signal about classification reliability. These kappa scores drive structured loop decisions: </span><b>Continue</b><span style="font-weight: 400;"> when the system is healthy, </span><b>WidenAudit</b><span style="font-weight: 400;"> when label noise is suspected, </span><b>FreezeAndAudit</b><span style="font-weight: 400;"> when quality declines for two or more iterations, and </span><b>DataProblem</b><span style="font-weight: 400;"> when labels or taxonomy appear fundamentally broken and the system should halt and escalate. This prevents the iteration loop from shipping regressions to production.</span></p> <p><span style="font-weight: 400;">For imbalanced taxonomies, we use metrics that expose rare-class failures. Accuracy alone can be misleading: A classifier that labels everything as non-sensitive may look accurate if sensitive assets are rare. Matthews correlation coefficient, macro F1, per-class recall, balanced accuracy, and calibration checks give a more complete picture.</span></p> <p><span style="font-weight: 400;">We also look for fragile decisions. One useful test is counterfactual masking: Remove one context field at a time and classify again. If the decision flips when a single weak signal disappears, the asset is flagged for review. The original prediction may still be correct, but the reasoning may be too brittle for confident automation.</span></p> <p><span style="font-weight: 400;">When quality drops, the system should slow down or stop. That can mean widening the audit sample, freezing optimization, or escalating a taxonomy or labeling problem for human review. A learning system needs brakes, not just accelerators.</span></p> <h3><span style="font-weight: 400;">6.) Distill Stable Behavior Into Rules</span></h3> <p><span style="font-weight: 400;">Even a strong LLM classifier should not be the default enforcement path forever. This distillation (not autonomous decision-making) is where we concentrate the model&#8217;s value. Any rule that could change how sensitive data is protected is reviewed and approved by a person before it goes live.</span></p> <p><span style="font-weight: 400;">As the system collects reviewed labels and decision traces, it can identify patterns that are stable enough to encode as deterministic rules. A rule might capture a high-precision semantic annotation, a reliable ownership and lineage combination, or a repeated pattern across a class of assets.</span></p> <p><span style="font-weight: 400;">Candidate rules go through validation before they affect serving decisions. A typical flow looks like this:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Propose a rule from stable context and label patterns.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Test it against a held-out reviewed set.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Run it in shadow mode on production-like traffic without changing serving behavior.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Promote it only if quality, coverage, and regression checks clear the required gates.</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Retire or revise it if the pattern becomes stale or quality degrades.</span></li> </ul> <p><span style="font-weight: 400;">Distillation operates in stages of increasing complexity:</span></p> <p><b>Stage 1: Field-based rules.</b><span style="font-weight: 400;"> Extract single-field patterns (exact match, keyword, numeric range, value-set membership, namespace patterns), with a minimum support of two assets and minimum purity of 80%.These are candidate-mining thresholds for surfacing rules to evaluate, not promotion thresholds. Every candidate from any stage still has to clear holdout validation, a higher dev-precision bar, shadow mode, and human review where protection could change before it can serve. </span></p> <p><b>Stage 2: Composite rules.</b><span style="font-weight: 400;"> For uncovered categories, search for conjunctions (e.g., &#8220;on-call contains X AND semantic type is ACCOUNT_ID&#8221;) under stricter gates — 95% purity, 10 examples minimum, and a stability check on 50% subsamples. </span></p> <p><b>Stage 3 (optional): LLM-assisted rule generation</b><span style="font-weight: 400;">. The model proposes custom conditions combining lineage depth with ownership patterns that manual heuristics miss, gated by rollout controls and default-off. Each candidate rule then proceeds through: holdout validation → blacklist if failed (bounded-TTL) → shadow mode (log, don&#8217;t apply) → promote to rules.yaml only if quality gates clear. Promoted rules shrink the LLM surface area.</span></p> <p><span style="font-weight: 400;">The important principle is that deterministic rules should not quietly reduce protection. Rule promotion needs safeguards that are designed to catch regressions, especially for sensitive classes.</span></p> <p><span style="font-weight: 400;">Validated rules are exported to Python, SQL, JSON, or Hack for deployment in production systems with zero LLM dependency. We manage these rollouts using compare-and-swap (CAS) semantics: We write immutable rule and prompt versions, then activate them via a lease-guarded compare-and-swap on the published pointer (atomic within our single-writer model). This ensures the production path remains a deterministic engine, while the LLM is reserved solely for novel assets that lack rule coverage.</span></p> <p><span style="font-weight: 400;">This is what makes the hybrid approach sustainable. LLMs help the system learn. Deterministic rules help the system enforce.</span></p> <h3><span style="font-weight: 400;">7.) Automate the Right Things</span></h3> <p><span style="font-weight: 400;">Automation is necessary, but the boundary matters.</span></p> <p><span style="font-weight: 400;">We automate context acquisition, evidence brief generation, candidate classification, evaluation runs, failure analysis, and candidate rule proposal. These are high-volume tasks where automation can reduce manual toil and make the process more consistent.</span></p> <p><span style="font-weight: 400;">We keep human review in the places where judgment matters – ambiguous policy interpretation, reviewed reference labels, high-risk disagreements, and promotion decisions that could materially affect protection. This is a routing policy, not a prompt. </span></p> <p><span style="font-weight: 400;">A decision is escalated for human review when any of the following hold:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><b>Low calibrated confidence</b><span style="font-weight: 400;">. The calibrated confidence falls below the auto-accept threshold, so the decision is not safe to ship automatically.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Judge-panel disagreement</b><span style="font-weight: 400;">. The three independent judges produce no clear majority, or inter-rater agreement (Cohen&#8217;s kappa) is low, a signal that the case is genuinely ambiguous.</span></li> <li style="font-weight: 400;" aria-level="1"><b>High-cost rare class</b><span style="font-weight: 400;">. The candidate is a rare sensitive category where a false negative is expensive, so the asymmetric error cost warrants a human check even at moderate confidence.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Fragile reasoning</b><span style="font-weight: 400;">. Counterfactual masking flips the label when a single weak signal is removed.The prediction may still be right, but the reasoning is too brittle for confident automation.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Protection-reducing rule promotion</b><span style="font-weight: 400;">. A candidate rule would change enforcement for a sensitive class in a way that could reduce protection. Deterministic rules should not quietly weaken it.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Controller escalation</b><span style="font-weight: 400;">. The tuning controller enters Pausing or Diagnosing, indicating a quality concern or a fundamental labeling or taxonomy problem that a human must resolve.</span></li> </ul> <p><span style="font-weight: 400;">That balance is deliberate. Privacy-aware infrastructure should not hide uncertainty. If the model, judge, or evaluation loop disagrees, the system should surface that disagreement as a useful signal. Sometimes the right answer is not a better prompt. Sometimes the right answer is clearer policy guidance, better labels, or a narrower taxonomy.</span></p> <p><span style="font-weight: 400;">The best automation in this space does not replace people. It concentrates human attention on the hardest cases, records the reasoning, and turns stable learning into repeatable enforcement over time.</span></p> <h2><span style="font-weight: 400;">What We Learned</span></h2> <figure id="attachment_24124" aria-describedby="caption-attachment-24124" style="width: 1999px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="size-full wp-image-24124" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png" alt="" width="1999" height="1234" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png 1999w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=916,565 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=768,474 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=1024,632 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=1536,948 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=96,59 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-figure-8-updated.png?resize=192,119 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24124" class="wp-caption-text">Figure 8: Seven principles separate a robust hybrid classifier from a naive “just ask the model” approach. Each row contrasts the failure mode (left) with the design choice that fixes it (right) — favoring richer context, replayable decisions, honest metrics, an uncontaminated reference set, quality-gated coverage, distillation into rules, and a controller that knows when to stop.</figcaption></figure> <h3><span style="font-weight: 400;">Context Quality Beats Prompt Quality</span></h3> <p><span style="font-weight: 400;">When classification stalls, it is tempting to keep tuning the prompt. In our experience, better context often matters more. Code resolution, lineage, ownership, and semantic annotations can change the decision space in a way prompt edits cannot.</span></p> <p><span style="font-weight: 400;">The practical lesson is simple: Before asking whether the model needs a better instruction, ask whether it has the evidence a human reviewer would need. We saw this with a field named </span><span style="font-weight: 400;">age</span><span style="font-weight: 400;"> in a caching pipeline. It was a cache TTL, not a person&#8217;s age, and prompt-only changes did not fix it reliably, adding code resolution and lineage did. Once the model could see that the field resolved to a TTL, the false positive went away.</span></p> <h3><span style="font-weight: 400;">Determinism Means Replayability</span></h3> <p><span style="font-weight: 400;">The goal is not to make an LLM produce the same text every time. The goal is to reproduce a decision later using the same versioned inputs, context, and logic.</span></p> <p><span style="font-weight: 400;">That is why versioning matters. A useful decision trace should tell us what evidence was used, which rule or prompt version was active, and how the decision can be replayed during debugging, incident review, or audit support. In one review, we replayed a single past classification from its stored decision trace and the pinned context, rule, and prompt versions, and reconstructed exactly why the asset received the label it did, without rerunning the LLM.</span></p> <h3><span style="font-weight: 400;">Accuracy Alone Is Not Enough</span></h3> <p><span style="font-weight: 400;">For imbalanced taxonomies, accuracy can hide the failures that matter most. If a sensitive category is rare, a classifier can look good while missing too many examples of that category.</span></p> <p><span style="font-weight: 400;">Balanced metrics, per-class recall, calibration checks, and review of false negatives are all part of the quality picture. No single metric carries the whole story. We saw a classifier that labeled almost everything non-sensitive show a high overall accuracy while its per-class recall on a rare sensitive category stayed low. Matthews correlation coefficient and macro F1 surfaced the gap that accuracy hid, and the misses became the cases we routed back for review.</span></p> <h3><span style="font-weight: 400;">Keep Recommendation Separate From Truth</span></h3> <p><span style="font-weight: 400;">Model-generated labels are useful, but they should not automatically become reference labels. The reference set needs reviewed provenance, and holdout evaluation should not be contaminated by the same model outputs being evaluated.</span></p> <p><span style="font-weight: 400;">This separation adds friction by design. It is the friction that prevents a self-reinforcing loop from looking better while becoming less grounded. We saw the pattern directly. An optimization run scored against the same model&#8217;s earlier labels appeared to improve, but when we re-evaluated it against the frozen human-reviewed reference set, the apparent gains turned out to drift away from policy intent.</span></p> <h3><span style="font-weight: 400;">Coverage Is Not Correctness</span></h3> <p><span style="font-weight: 400;">Higher automation coverage is only useful if quality holds. A classifier can auto-resolve more assets while becoming less reliable on the cases that matter.</span></p> <p><span style="font-weight: 400;">That is why coverage should be tracked alongside recall, precision, regression checks, and robustness tests. The goal is not to classify more assets automatically at any cost. It is to automate the cases that are stable enough to automate. In one case, promoting a broad rule lifted automation coverage but dropped shadow-mode per-class recall on a sensitive class. Because we track coverage alongside recall, we caught the regression and narrowed the rule before it reached serving.</span></p> <h3><span style="font-weight: 400;">Distillation Is the Production Model</span></h3> <p><span style="font-weight: 400;">LLMs are useful for ambiguity, cold start, and new patterns. Deterministic logic is better for the routine path where decisions need to be fast, explainable, and reproducible.</span></p> <p><span style="font-weight: 400;">The sustainable model is a funnel: Let LLMs help discover and reason, then distill stable patterns into versioned rules that enforcement systems can run efficiently.</span></p> <h3><span style="font-weight: 400;">Self-Regulation Is Architectural, Not Operational</span></h3> <p><span style="font-weight: 400;">A learning system that does not know when to stop is a potential liability. We built a tuning controller that transitions through regimes:</span></p> <ul> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Observing (gathering signal). </span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Maintaining (healthy iteration).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Conserving (gains slowing).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Pausing (quality concerns).</span></li> <li style="font-weight: 400;" aria-level="1"><span style="font-weight: 400;">Diagnosing (halt for fundamental issues). </span></li> </ul> <p><span style="font-weight: 400;">In practice, the oscillation detector identifies stalled optimization, classifiers cycling between two candidate prompts without improving, and terminates them early, saving thousands of wasted classification calls per stalled run. This self-regulation was designed into the architecture from the start; retrofitting it would have been significantly harder.</span></p> <figure id="attachment_24106" aria-describedby="caption-attachment-24106" style="width: 1996px" class="wp-caption alignnone"><img loading="lazy" decoding="async" class="wp-image-24106 size-full" src="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png" alt="" width="1996" height="1310" srcset="https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png 1996w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=916,601 916w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=768,504 768w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=1024,672 1024w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=1536,1008 1536w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=96,63 96w, https://engineering.fb.com/wp-content/uploads/2026/06/Privacy-Aware-Infrastructure-in-the-AI-Native-Era-Figure-9-e1782178824141.png?resize=192,126 192w" sizes="auto, (max-width: 992px) 100vw, 62vw" /><figcaption id="caption-attachment-24106" class="wp-caption-text">Figure 9: The controller is a state machine, not a retry loop. It escalates only as severity demands, <span style="font-family: 'courier new', courier;">Maintaining</span> → <span style="font-family: 'courier new', courier;">Conserving</span> → <span style="font-family: 'courier new', courier;">Pausing</span>, and can recover back down when health returns (dashed). Crucially, <span style="font-family: 'courier new', courier;">Diagnosing</span> is an absorbing state: once the systemic fault repeats, the loop halts and hands off to a human rather than burning budget on more retries.</figcaption></figure> <h2><span style="font-weight: 400;">Upcoming Directions</span></h2> <p><span style="font-weight: 400;">Three directions follow from this work:</span></p> <ol> <li style="font-weight: 400;" aria-level="1"><b>Migrate legacy classifiers</b><span style="font-weight: 400;"> to this system, replacing ad-hoc heuristics with the full context-mesh + distillation pipeline.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Expand to other PAI workflows:</b><span style="font-weight: 400;"> The same pattern (context → LLM reasoning → distillation → deterministic enforcement) applies to lineage validation, purpose-boundary checking, and retention policy assignment.</span></li> <li style="font-weight: 400;" aria-level="1"><b>Apply beyond privacy:</b><span style="font-weight: 400;"> Early experiments suggest these techniques generalize to agent observability and oversight, where the same tension exists between probabilistic reasoning and auditable enforcement.</span></li> </ol> <h2><span style="font-weight: 400;">AI-Native Products Raise the Bar for PAI</span></h2> <p><span style="font-weight: 400;">AI-native products raise the bar for privacy-aware infrastructure. They create new data modalities, faster iteration cycles, and more ambiguous signals. At the same time, privacy enforcement still needs decisions that are consistent, explainable, and reproducible.</span></p> <p><span style="font-weight: 400;">Asset classification shows how to bridge that gap. Start with a clear contract. Build rich context. Use LLMs for novelty and ambiguity. Keep reviewed labels separate from model recommendations. Evaluate with metrics that expose rare-class failures. Distill stable behavior into deterministic, versioned rules.</span></p> <p><span style="font-weight: 400;">That pattern lets the system learn from ambiguity without making ambiguity the foundation of enforcement.</span></p> <p><span style="font-weight: 400;">The pattern also generalizes beyond our own use. A separate enforcement team compared this pattern against three alternatives head-to-head and chose it for their classification layer, independently of our work. In their evaluation, deterministic-first classification with LLM fallback produced more consistent, debuggable, and auditable decisions than end-to-end LLM approaches. Two teams independently arriving at the same trade-off (reasoning with LLMs, enforcing with rules) suggests a robust pattern.</span></p> <p><span style="font-weight: 400;">The broader lesson is that privacy-aware infrastructure is not a tax on engineering. It is a driving force for better architecture: clearer contracts, richer context, stronger evaluation, safer publication, and systems that know when to ask for human judgment.</span></p> <h1><span style="font-weight: 400;">Acknowledgements</span></h1> <p><em><span style="font-weight: 400;">The authors would like to acknowledge the contributions of many members of the Privacy-Aware Infrastructure team who have played a crucial role in the work described here. In particular, we extend special thanks to (in alphabetical order) Fanghao Song, Kartikey Sachdeva, and Loka Potnuru for their foundational contributions to classifier analysis, runtime feature migration, scanner hardening, false-positive reduction, and age-flow precision improvements — as well as the broader PAI team for context enrichment and evaluation.</span></em></p> <p><em><span style="font-weight: 400;">We are also grateful to Inchara Shivalingaiah, Juemin Wei, Nithya Arumugam, Zhe Wang, Dave Kurtzberg, and team for independently validating the classification pattern within their autonomous remediation pipeline, and to Deborah Davis for editorial guidance throughout.</span></em></p> <p><em><span style="font-weight: 400;">Shout out to Alex Basiuk whose ideas and good steer made this go from a whiteboard sketch to a working prototype in days.</span></em></p> <p><em><span style="font-weight: 400;">Special thanks to Jonathan Bergeron for insightful feedback and suggestions.</span></em></p> <p>The post <a href="https://engineering.fb.com/2026/06/25/security/privacy-aware-infrastructure-in-the-ai-native-era-an-asset-classification-case-study/">Privacy-Aware Infrastructure in the AI-Native Era: An Asset Classification Case Study</a> appeared first on <a href="https://engineering.fb.com">Engineering at Meta</a>.</p>