This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
mobilecritical PriorityID: skiko-metal-lifecycle-sync

Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization

Enforces strict synchronization between iOS application lifecycle notifications (UIApplicationDidEnterBackgroundNotification / UIApplicationWillEnterForegroundNotification) and Skiko's Metal surface rendering loop. Prevents GPU command buffer execution after background suspension, which triggers iOS termination code 0x8badf00d and Metal device invalidation (MTLDeviceWasRemoved). Mandates blocking synchronous wait-for-completion on pending Metal command buffers prior to background transition acknowledgment.

Prohibited Anti-Pattern: Asynchronous or Uncoordinated iOS Background Transitions During Active Metal Draw Cycles

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization - Enforces strict synchronization between iOS application lifecycle notifications (UIApplicationDidEnterBackgroundNotification / UIApplicationWillEnterForegroundNotification) and Skiko's Metal surface rendering loop. Prevents GPU command buffer execution after background suspension, which triggers iOS termination code 0x8badf00d and Metal device invalidation (MTLDeviceWasRemoved). Mandates blocking synchronous wait-for-completion on pending Metal command buffers prior to background transition acknowledgment.
globs: **/iosMain/**/*.kt, **/appleMain/**/*.kt, **/iosApp/**/*.swift, **/iosApp/**/*.m
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/skiko-metal-lifecycle-sync"
---

# Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization

> **Severity**: CRITICAL | **Category**: mobile
> **Anti-Pattern Warning**: Asynchronous or Uncoordinated iOS Background Transitions During Active Metal Draw Cycles

## System Constraints
You are a Principal Engineer enforcing Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization. Mandate that all Metal command buffer submissions, Skiko layer drawing operations, and iOS application lifecycle events are strictly serialized and synchronized. When iOS suspends an application, all in-flight Metal rendering commands must be flushed and blocked until completed on the GPU, and the Skiko render loop must be paused before entering the background state to prevent SIGSEGV, GPU driver watchdog panics, and process termination by SpringBoard.

## Strict Guidelines
- Engineers MUST ALWAYS register observer hooks for UIApplication.didEnterBackgroundNotification and UIApplication.willEnterForegroundNotification directly on the UIViewController hosting the Compose UIViewController.
- Engineers MUST synchronously pause the SkikoViewController or SkiaLayer render loop before the background transition observer returns control to the operating system.
- Code MUST flush and await completion of the active MTLCommandBuffer via waitUntilCompleted() or waitUntilScheduled() prior to completing UIApplicationDelegate.applicationDidEnterBackground.
- Services MUST NOT dispatch Metal presentation calls (MTLCommandBuffer.present) or Skiko redraw dispatches (SkiaLayer.needRedraw) when the application state is UIApplicationStateBackground.
- Components NEVER re-instantiate or release the underlying CAMetalLayer or MTLDevice on background threads; all lifecycle state transitions must occur strictly on the main dispatch queue (DispatchQueue.main).

## Bad vs Good Implementations
### Synchronizing Skiko Render Delegation with iOS Lifecycle Events

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Failing to observe application background transitions leaves the Skiko Metal rendering loop active. Submitting Metal command buffers or presenting drawables while in the background violates iOS sandbox constraints and causes SpringBoard termination or Metal GPU assertion panics.
```swift
import UIKit
import shared

class ComposeContainerController: UIViewController {
    private var composeViewController: UIViewController?

    override func viewDidLoad() {
        super.viewDidLoad()
        let controller = MainViewControllerKt.MainViewController()
        self.composeViewController = controller
        addChild(controller)
        view.addSubview(controller.view)
        controller.didMove(toParent: self)
        // ANTI-PATTERN: Ignoring lifecycle notifications.
        // Active Skiko Metal rendering loop continues submitting MTLCommandBuffers
        // when the app is suspended, leading to SpringBoard 0x8badf00d termination or GPU crash.
    }
}
```

#### Production Standard (Correct)
> **Standard**: Explicitly subscribes to UIApplication background and foreground notifications on the main thread, routing suspension commands to the shared Kotlin Skiko lifecycle bridge before iOS freezes the process, guaranteeing zero background GPU command submission.
```swift
import UIKit
import shared

final class ComposeContainerController: UIViewController {
    private var composeViewController: UIViewController?
    private var lifecycleObservers: [NSObjectProtocol] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        let controller = MainViewControllerKt.MainViewController()
        self.composeViewController = controller
        addChild(controller)
        controller.view.frame = view.bounds
        controller.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(controller.view)
        controller.didMove(toParent: self)

        setupLifecycleCoordination()
    }

    private func setupLifecycleCoordination() {
        let center = NotificationCenter.default
        
        let backgroundObserver = center.addObserver(
            forName: UIApplication.didEnterBackgroundNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            // Synchronously halt Compose/Skiko rendering and wait for GPU completion
            SkikoLifecycleBridgeKt.suspendSkikoMetalSurface()
            self?.composeViewController?.view.isHidden = true
        }

        let foregroundObserver = center.addObserver(
            forName: UIApplication.willEnterForegroundNotification,
            object: nil,
            queue: .main
        ) { [weak self] _ in
            self?.composeViewController?.view.isHidden = false
            SkikoLifecycleBridgeKt.resumeSkikoMetalSurface()
        }

        lifecycleObservers = [backgroundObserver, foregroundObserver]
    }

    deinit {
        lifecycleObservers.forEach { NotificationCenter.default.removeObserver($0) }
    }
}
```

### Coordinated Skiko Surface Suspension in Kotlin Multiplatform (iosMain)

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Ignoring the bridge implementation or dispatching rendering state teardown asynchronously fails to flush the GPU hardware pipeline before the OS process freeze, causing memory corruption when wake-up occurs.
```kotlin
// iosMain
package com.example.app

import org.jetbrains.skiko.SkiaLayer

// ANTI-PATTERN: Relying on automatic OS handling for SkiaLayer
fun suspendSkikoMetalSurface() {
    // No-op or purely async dispatch without waiting for in-flight Metal command buffers
    println("Application entered background - ignoring Metal surface state")
}
```

#### Production Standard (Correct)
> **Standard**: Uses a synchronized lifecycle coordinator that commits a barrier command buffer and synchronously calls waitUntilCompleted() on the main queue before allowing background suspension to complete, ensuring all Metal work finishes cleanly.
```kotlin
// iosMain
package com.example.app

import kotlinx.cinterop.ExperimentalForeignApi
import platform.Metal.MTLCommandBufferProtocol
import platform.Metal.MTLCommandQueueProtocol
import platform.darwin.dispatch_sync
import platform.darwin.dispatch_get_main_queue

@OptIn(ExperimentalForeignApi::class)
object SkikoMetalLifecycleCoordinator {
    private var activeQueue: MTLCommandQueueProtocol? = null
    private var isSuspended = false

    fun registerCommandQueue(queue: MTLCommandQueueProtocol) {
        activeQueue = queue
    }

    fun suspendSurface() {
        dispatch_sync(dispatch_get_main_queue()) {
            if (isSuspended) return@dispatch_sync
            isSuspended = true
            
            // Flush pending tasks and guarantee GPU has finished scheduled command buffers
            activeQueue?.let { queue ->
                val barrierBuffer = queue.commandBuffer()
                barrierBuffer?.commit()
                barrierBuffer?.waitUntilCompleted()
            }
        }
    }

    fun resumeSurface() {
        dispatch_sync(dispatch_get_main_queue()) {
            isSuspended = false
        }
    }

    fun canRender(): Boolean = !isSuspended
}

fun suspendSkikoMetalSurface() = SkikoMetalLifecycleCoordinator.suspendSurface()
fun resumeSkikoMetalSurface() = SkikoMetalLifecycleCoordinator.resumeSurface()
```

Recommended MCP Servers (mcp-serverlist.com)

Equip agent with system tools to verify code for this stack.

Recommended Agent Skills (askills.dev)

Ready-to-use executable skill functions tailored to this stack.

Architectural Specification & Invariant Rationale

Architectural Invariant Design: Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization

Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization defines an industrial-grade behavioral standard for mobile engineering. Enforces strict synchronization between iOS application lifecycle notifications (UIApplicationDidEnterBackgroundNotification / UIApplicationWillEnterForegroundNotification) and Skiko's Metal surface rendering loop. Prevents GPU command buffer execution after background suspension, which triggers iOS termination code 0x8badf00d and Metal device invalidation (MTLDeviceWasRemoved). Mandates blocking synchronous wait-for-completion on pending Metal command buffers prior to background transition acknowledgment. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Asynchronous or Uncoordinated iOS Background Transitions During Active Metal Draw Cycles Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftAsynchronous or Uncoordinated iOS Background Transitions During Active Metal Draw CyclesCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS register observer hooks for UIApplication.didEnterBackgroundNotification and UIApplication.willEnterForegroundNotification directly on the UIViewController hosting the Compose UIViewController.

Engineers MUST ALWAYS register observer hooks for UIApplication.didEnterBackgroundNotification and UIApplication.willEnterForegroundNotification directly on the UIViewController hosting the Compose UIViewController.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST synchronously pause the SkikoViewController or SkiaLayer render loop before the background transition observer returns control to the operating system.

Engineers MUST synchronously pause the SkikoViewController or SkiaLayer render loop before the background transition observer returns control to the operating system.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Code MUST flush and await completion of the active MTLCommandBuffer via waitUntilCompleted() or waitUntilScheduled() prior to completing UIApplicationDelegate.applicationDidEnterBackground.

Code MUST flush and await completion of the active MTLCommandBuffer via waitUntilCompleted() or waitUntilScheduled() prior to completing UIApplicationDelegate.applicationDidEnterBackground.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Services MUST NOT dispatch Metal presentation calls (MTLCommandBuffer.present) or Skiko redraw dispatches (SkiaLayer.needRedraw) when the application state is UIApplicationStateBackground.

Services MUST NOT dispatch Metal presentation calls (MTLCommandBuffer.present) or Skiko redraw dispatches (SkiaLayer.needRedraw) when the application state is UIApplicationStateBackground.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Components NEVER re-instantiate or release the underlying CAMetalLayer or MTLDevice on background threads

Components NEVER re-instantiate or release the underlying CAMetalLayer or MTLDevice on background threads; all lifecycle state transitions must occur strictly on the main dispatch queue (DispatchQueue.main).

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is Compose Multiplatform Skiko Metal Surface Lifecycle Synchronization critical for AI coding assistants?

Enforces strict synchronization between iOS application lifecycle notifications (UIApplicationDidEnterBackgroundNotification / UIApplicationWillEnterForegroundNotification) and Skiko's Metal surface rendering loop. Prevents GPU command buffer execution after background suspension, which triggers iOS termination code 0x8badf00d and Metal device invalidation (MTLDeviceWasRemoved). Mandates blocking synchronous wait-for-completion on pending Metal command buffers prior to background transition acknowledgment. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Asynchronous or Uncoordinated iOS Background Transitions During Active Metal Draw Cycles) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

Related MOBILE Rules

View all rules →
1 Rule in Stack

Compose