I updated the Visual Store .
In the previous article “Added the ability to retain judgment results in Visual Store” (linked to 2026-09-21-2.md), I wrote about the new Judgment Layer that saves the decisions and verification results before an image is opened. This time, rather than introducing a new feature, I’m addressing the issue where the default build with VP9 enabled fails to compile on Debian 12 ARM64, and I’ve revised the procedure for reporting bugs in the Visual Store Skill. (related article)
Default build failed on Debian 12 ARM64
Visual Store compresses sequential PNGs temporally using VP9, so it directly links Rust to the system’s libvpx. It does not use a configuration that invokes FFmpeg; instead, it links against libvpx found via pkg-config.
Until now, the development environment mainly used macOS ARM64 with libvpx 1.16.0. When using the libvpx 1.12.0 that comes with Debian 12 ARM64 and performing a locked installation as described in the README, the compilation halted.
cargo install --path . --locked
The main errors consisted of the following three points.
vpx_codec_error:
expected *mut vpx_codec_ctx, found &vpx_codec_ctx
vpx_codec_error_detail:
expected *mut vpx_codec_ctx, found &vpx_codec_ctx
slice::from_raw_parts:
length expected usize, found u64 (frame.sz)
By adding --no-default-features, the installation succeeded. The PNGs init, put, features, judgment, and verify can also be used.
However, this merely disables the VP9 backend, so a completed run cannot be pack --codec vp9. The fact that Visual Store’s default workflow still cannot finish on Debian 12 remains unchanged.
This issue was confirmed in Issue #23 “Default VP9 build produces type errors with Debian 12’s libvpx bindings” and Issue #24 “Default VP9 feature fails to compile with Debian 12’s libvpx 1.12” (links omitted). (issue #23) (issue #24)
The same crate yields different types depending on the system headers
Visual Store pins libvpx-native-sys 5.0.17. However, the FFI bindings actually used can differ in type depending on the headers and environment of the system libvpx they interface with.
In this case, the bindings on Debian 12 required vpx_codec_error and vpx_codec_error_detail—functions used to retrieve error strings—to accept *mut vpx_codec_ctx_t. The existing implementation passed a shared reference of &vpx_codec_ctx_t, so the types did not match.
Simply casting to a raw pointer would allow the compilation to proceed, but creating a mutable pointer from a shared reference makes it difficult to explain its relationship with Rust’s aliasing guarantees.
Therefore, we changed the context passed to the error‑handling code into an exclusively mutable reference.
fn check(
operation: &'static str,
status: ffi::vpx_codec_err_t,
context: Option<&mut ffi::vpx_codec_ctx_t>,
) -> Result<()>
The caller already treats the encoder or decoder context as mutable. By keeping that borrow alive through the diagnostic processing, we can pass it to both the older header’s required mutable pointer and the newer environment’s signature without resorting to a cast that forcibly removes constness.
Since the error string is owned by libvpx, we copy it into a Rust string on the spot. We retained the design that does not retain the C‑side pointer beyond the lifetime of the context.
Do not treat frame.sz as a slice length directly
Another issue concerns the type of the packet size returned by the encoder.
In the newer environment the existing code compiled without change, but on Debian 12 ARM64 frame.sz became u64, and std::slice::from_raw_parts required usize, so it could not be passed directly.
This can also be made to work with a conversion using as usize, allowing compilation to succeed. However, if an unrepresentable value is encountered, it may be truncated. Moreover, a Rust slice must not exceed isize::MAX bytes in total.
After the fix, the packet size is checked in the following order.
libvpx frame size
↓ TryInto<usize>
Error if it cannot be expressed as a usize
↓
Error if it exceeds isize::MAX
↓
Create a slice with the verified length
It is unlikely that a normal packet will actually reach this upper limit. Still, because this integer received from the FFI is used as the length of a memory slice, we did not resort to a cast merely to silence the compiler.
Added Debian 12 ARM64 to CI
The fix was applied in PR #26 “Fix Debian 12 ARM64 libvpx build and validate VP9”. (PR #26)
We added a job to the CI that runs on a ubuntu-24.04-arm runner and uses the official rust:1.98.1-bookworm container. This job includes Debian 12’s libvpx-dev 1.12.0 and pkg-config.
The verification goes beyond just compilation.
cargo install --path . --locked
Unit test for the VP9 codec
Test for the time‑domain codec
Test for atomicity and retries of packing
VP9 round-trip example
CLI workflow: init → put ×3 → pack → verify
In the final CLI verification, we register the same PNG three times, actually pack them into a VP9 segment, and then verify the entire store. Even if the bindings compile successfully, any breakage in encoder or decoder calls will cause this step to fail.
The supported range of system libvpx is documented in the README and ADR as versions from 1.12.0 up to 1.16.0. In addition to the lower‑bound Debian 12 ARM64 and the 1.16.0 we use during development, the existing CI that relies on Ubuntu and macOS system packages continues to run.
Two issues for the same bug were created
This time, Issues #23 and #24 were created, each handling almost identical reproduction conditions and errors.
Separate from the code problem, there was also a gap in the procedure for reporting bugs in the Visual Store Skill. It was not explicitly stated that before opening a new issue, one should search both open and closed issues and read not only the title but also the full description.
We added the following step to the Skill:
Verify whether the issue can be reproduced
↓
Search using altered notations of error code, command, symptoms, and reproduction conditions
↓
Read candidate texts for both open and closed issues
↓
If the cause or reproduction conditions match an existing issue, direct to that issue
↓
Create a new issue only when no matching issue exists
Even if the symptoms resemble those of a previously fixed issue, we still allow opening a new issue when the problem resurfaces in a different version. In such cases, however, you should first verify what differs—such as the version gap—from the existing issue.
We also added the requirement that any logs, paths, images, or metadata attached to an issue must have any confidential information removed, and that rejections based on the specification or unconfirmed speculation should not be reported as confirmed bugs.
In addition, we unified the Visual Store Skill, the CLI reference, and the VP9 codec ADR into English. While this does not change the CLI’s core functionality, it standardizes the steps presented to coding agents in a form that is less prone to misinterpretation of the decision criteria.
Summary
The changes made in this implementation were modest, focusing mainly on how the context is borrowed and how packet lengths are converted. However, because we directly link to the system library, merely succeeding with the newer libvpx on our development machine does not mean the problem is solved.
By pinning Debian 12 ARM64 and libvpx 1.12.0 in the CI, we can now verify the entire workflow—from installing the default features to packing and verifying VP9—even on older stable distributions.
At the same time, creating two issues for the same bug revealed that issue‑search procedures are also needed for the agent’s Skill. We addressed not only the implementation but also the process of detecting and reporting bugs as part of the operational workflow.