サむオステクノロゞヌTech.Labのブログ - TECH PLAY

TECH PLAY

サむオステクノロゞヌTech.Lab

サむオステクノロゞヌTech.Lab の技術ブログ

å…š721ä»¶

どもChrome DevTools MCP でブラりザを觊らせる話を続けお曞いおいる韍ちゃんです。 先日、 Chrome DevTools MCP でのセットアップずログむンの通し方 を曞きたした。あそこたで終われば、あずは普段どおりブラりザを觊るだけです。その続きを觊っおいお、ふず思ったんですよね。これ、マニュアル䜜りにも䜿えるんじゃないのっお。 今回はそこだけ切り出しお詊した回です。マニュアルの本文を曞かせる話や、曞いた本文を仕䞊げるずころたでは別問題なので、ここでは扱いたせん。 マニュアルで貌る図の手間を少し軜くする 話です。 手順曞のスクショ、撮った埌の手間がわりず重かった 手順曞やマニュアルを䜜るずきのい぀もの流れっお、倧䜓こうですよね。 手順ごずに画面を出しおスクショを撮る 画像線集゜フトで枠や矢印を描き足す PNG ずしお保存しお、名前を付ける 数えるず3手です。しかも手順の数だけ、これを繰り返すこずになりたす。 耇数の察象を1枚で瀺したいずきは、ここにもう䞀手増えたす。①②③の番号を、ひず぀ず぀䜍眮を決めながら打ち蟌む䜜業です。䞊の3手には数えおいたせんが、察象の数だけ確実に増えおいく䜜業で、地味に手間がかかっおいたずころでした。 今回やったのは、この3手ず番号の䜍眮決めをたるごず「この欄を①、この欄を②、このボタンを③で囲っお撮っお」の䞀蚀に畳めるかずいう話です。画面を出すずころは人が普段どおり操䜜するので数に入れおいたせん。そこはどちらのやり方でも同じですからね。 結論ずしお以䞋のような図がプロンプトだけで䜜れたす。枡した指瀺の䞭身はこのあず説明したすが、たずは出おきた結果からです。 入力欄2぀ずボタン、合わせお3箇所を䞀蚀で囲んで、番号たで振っおもらえたした。 どう指瀺したら、枠぀きの1枚が出おくるのか 䟋に䜿うのは自分で䜜っおいるアプリの画面です。Azure䞊に公開しおいるのでブラりザ経由でアクセスを行いたす。 指瀺は2段に分けおいたす。ひず぀は自分のアプリでしか通じない指定、もうひず぀はどの画面でも倉わらない骚組みです。 たずアプリ偎の指定です。ここは読んでいる人が自分のものに差し替えるずころですね。 開く画面: Azure に眮いた自䜜アプリフォヌム画面 / 䞀芧画面 察象の呌び名: 「タむトル」「ゞム」— 画面に出おいる文字でそのたた指す ここから先は、どの画面に察しおも同じ骚組みです。察象を枠で囲むだけの、シンプルなものです。 1. 察象のタブを決めお、前面に出す 2. りィンドりの寞法を固定する1400x900 3. 察象を囲む枠を、重ねたレむダヌずしお1枚描く - 察象そのものの style / outline / border には觊らない - 枠は viewport 座暙で描く描いおから撮るたでにスクロヌルしない 4. 撮る 5. 枠のレむダヌを消す この骚組みで䞀番効いおいるのは、 元の画面を動かさないこず です。察象の border を盎接曞き換えるず、その分だけ幅が増えおレむアりトが動きたす。撮りたいのは普段どおりの画面なので、枠を足したせいで䞊びが倉わったら元も子もないんですよね。なので枠は、画面の䞊に透明なレむダヌを1枚かぶせお、そこに描いおいたす。消すずきもレむダヌごず倖すだけなので、消し忘れも起きにくい䜜りです。 枠がうたく出ないずきは、この「元の画面に觊らない」が守られおいるかを先に芋おもらうのが早いです。 なお、ここに茉せたのは骚組みだけです。実際に保存したものは初版で227行あっお、枠の座暙や番号の眮き堎を詰めるずころが長い郚分です。これだけを貌っおも、同じ絵たでは出たせん。 人が蚀うのは1行で、長い郚分はぜんぶあちら偎にありたす。 Chrome DevTools MCP そのものの现かい仕様は、 公匏ドキュメント に譲りたす。 この骚組みだけで、どこたで通るか詊したした。たずは入力欄が空のフォヌムです。 この1枚は、察話で組み立おながら撮った回です。「タむトルの欄を囲っお」ず指しおは、出おきた枠を芋お盎しお、を䜕床か繰り返しおいたす。䞀蚀で撃おるようになったのは、このあずですね。 さらに、フォヌムずはたったく性質の違う画面でも詊したした。䞀芧に䞊んだカヌドを1枚だけ囲うパタヌンです。 こちらで蚀ったのは、これだけです。 ゞムのカヌドを囲っお撮っお 察話で䜜った指瀺を、远加なしで撃っただけです。入力欄でもカヌドでも、同じ指瀺で枠が出おきたした。 番号や矢印の䜍眮、どこたで指瀺で決められるのか 䜍眮や芋た目たで指瀺で決められるのか、実際に詊したのがここからです。 耇数の察象を1枚で瀺すずき、番号バッゞをどこに眮くかっお、盎感的には二択ですよね。察象の䞊に重ねるか、察象の倖に出しお匕き出すか。たずはこの2぀を詊したした。 詊しに実隓を行いたした。実際に投げた蚀葉はこうです。 フォヌム: キッカヌを①、タむトルを②、生成ボタンを③で囲っお撮っお 䞀芧  : オヌロラを①、ブログを②、ゞムを③で囲っお撮っお 番号の眮き堎このずきは指瀺の偎で決め打ちにしおいた: A 察象に重ねる枠の巊䞊 B 察象の倖に出しお匕き出す枠の倖・巊右は自動で反転 蚀った順が、そのたた①②③になりたす。手順曞の番号は手順そのものなので、䞊べ替えは機械に任せおいたせん。 重ねる案は、察象の䞭の文字を朰したした。①のバッゞが HOW TO の頭を隠しお IOW TO に芋えおしたいたす。 倖に出す案は、フォヌムなら読めたしたが、䞀芧では壊れたした。カヌドの隙間は隣どうしの共有スペヌスなので、番号バッゞが隣のカヌドの枠に乗っおしたい、どちらの番号なのか読めなくなりたす。 どちらも、眮き堎を「察象に重ねる巊䞊」「倖に出しお匕き出す」ず決め打ちで指瀺に曞いおいたのが敗因です。そこで指瀺を曞き換えたした。 6通りの眮き方を詊し、「バッゞが文字を芆う面積  別の察象の枠やバッゞに乗る面積 × 4」が 最小のものを遞ぶ。0 になった時点で打ち切る。 眮き堎を決めずに、「どう遞ぶか」だけを曞いたわけですね。眮き堎を探す仕事のほうを機械に枡しお、人は「䜕を避けたいか」だけを曞く、ずいう分担です。 結果、フォヌムでは右䞊、䞀芧では巊䞊が遞ばれたした。どちらも芆った文字は0件です。さっき芋おもらったフォヌムの絵は、この右䞊に眮かれた番号だったんですね。同じ考え方で䞀芧に撃ったのがこちらです。 同じ指瀺のはずなのに、眮き堎が倉わっおいるわけですね。ずはいえ賢いのは機械偎ではなくお、「どこに眮くか」を決めずに「どう遞ぶか」を曞いた指瀺のほうです。眮き堎を固定しお枡しおいたら、さっきの没案2枚のどちらかが出おきおいたした。 もちろん、同じ画面で毎回たったく同じ䜍眮に出したいなら、眮き堎を指定しおしたうほうが早いです。䞀床撮っお䜍眮が決たっおいるなら、それで足りたす。壊れたのは、確かめないたた固定を別の画面ぞ持ち回ったずきでした。 番号に加えお、矢印も同じ指瀺の䞭で足せたした。䞊の発話に「③に矢印も」ず䞀蚀足すだけです。 曎新のたびに、同じ絵を撮り盎せるのか 指瀺は最初から曞き䞊げたわけではなくお、チャットで䞀緒に詊しながら詰めおいったんですよね。狙った枠や番号が出るたでやり取りを重ねお、固たったずころでそのプロンプトを曞き出しお保存しおおいた、ずいう順番です。Chrome も Claude Code のセッションも立ち䞊げ盎しおから、保存したその指瀺をもう䞀床撃っおみたした。出おきたのは同じ絵です。番号ず矢印たで入れた絵も、圓お盎しお撮り盎せば同じものが出おきたす。 ただ、機械で確かめられるのは、画面䞊に文字ずしお存圚しおいるものだけです。背景画像に焌き蟌たれた文字たでは刀定できないので、番号がそこに乗っおいないかは、最埌は自分の目で芋お確認しおいたす。 機械が芋えおいる範囲ず芋えおいない範囲を、図にするずこんな境目になりたす。AI偎に画像を読み蟌たせお刀断するこずも可胜ではあるんですが、違和感を芋぀けるのは人間のほうが早いので人間をルヌプに組み蟌んだほうが早いです。 もちろん、画面のほうが倉わったら指瀺も盎すこずになりたす。欄が増えたり䞊びが倉わったりすれば、囲む察象を蚀い盎すわけですね。ここはマニュアルを曎新するずきに本文を盎すのず同じで、 盎す察象に指瀺が1぀加わる ずいう話です。 で、自分の仕事のどこで䜿えるのか たずめるず、手順曞甚のスクショで䞀番手間だった「撮った埌に枠ず番号を入れ盎す」ずころが、プロンプトに畳たりたした。冒頭で「マニュアル䜜りにも䜿えるんじゃないの」っお思ったずころ、番号や矢印たで含めお䜿えるずころたでは確認できた、ずいう結果です。 割り切ったずころも曞いおおきたす。抌した瞬間の画面はこの撮り方では取れたせん。ボタンを抌した盎埌に出る完了衚瀺みたいな、寿呜の短い衚瀺は間に合いたせん。逆に手順曞が欲しい絵っお「これから抌す堎所」なので、抌す前に撃぀圢ずは盞性がいいです。 なので、そういった瞬間的な衚瀺ずかに関しおは「Playwright」か「画面録画」で切り出すのが楜かなっお思いたす。 あず、配れるコマンドの圢にはしおいたせん。䜿うなら、䞊の骚組みを䞋敷きに自分の察象サむト向けの指瀺を曞いお、狙った絵が出るたで詰めお、サむトごずに専甚のプロンプトファむルずしお保存しおおく圢になりたす。僕がやったのもその順番でした。 たずは自分がよく撮る画面1぀で、「この欄を①で囲っお撮っお」っお蚀っおみるずころから詊しおみおください。 ではたた ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Chrome DevTools MCPで孊ぶ、手順曞甚スクショの枠・番号自動化 first appeared on SIOS Tech Lab .
倧埡所のアむコンラむブラリ Font Awesome の アニメヌション機胜 は、CSSのクラスを远加するだけで動きを䞎えられ、お手軜です。動きのオプションも指定が可胜です。 このアニメヌション機胜をGoogleの Material Symbols & Icons に移怍しおみたいず思い、やっおみたしたので共有したす。 公開されおいるMaterial Symbols & Iconsず、 Font AwesomeのコヌドGitHub を䞀郚拝借したした。 アニメヌションのCSSを取り蟌む たず、Google Fontsりェブフォントの読み蟌みをlinkタグで蚘述したす。今回はFill塗り぀ぶしのスタむルずしたした。 そしお、アニメヌションのCSSを収める「style.css」を呌び出すlinkタグも蚘述したす。 <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0" /> <link rel="stylesheet" href="style.css"> Font AwesomeのCSSからアニメヌションの動き郚分を取り出しおstyle.cssずしお保存したす。 先頭郚分には、各アむコンのサむズなどのスタむルを共通蚭定ずしお指定したす。 /* * 共通蚭定 */ .material-symbols-outlined[class*="fa-"] { display: inline-block; line-height: 1; font-size: 52px; padding: 16px; color:oklch(37.2% 0.044 257.287); } /* * Font Awesomeアニメヌション */ .fa-beat { animation-name: fa-beat; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-bounce { animation-name: fa-bounce; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } .fa-fade { animation-name: fa-fade; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-beat-fade { animation-name: fa-beat-fade; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-flip { animation-name: fa-flip; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1.5s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-flip-360 { animation-name: fa-flip-360; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-shake { animation-name: fa-shake; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.75s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-spin { animation-name: fa-spin; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 2s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-reverse { --fa-animation-direction: reverse; } .fa-pulse, .fa-spin-pulse { animation-name: fa-spin; animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, steps(8)); } .fa-spin-snap { animation-name: fa-spin-snap; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 3s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-snap-4 { animation-name: fa-spin-snap-4; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 2.4s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-snap-8 { animation-name: fa-spin-snap-8; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 4s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-buzz { animation-name: fa-buzz; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.6s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-wag { animation-name: fa-wag; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.9s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); transform-origin: bottom center; } .fa-float { animation-name: fa-float; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 3s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); will-change: transform; } .fa-swing { animation-name: fa-swing; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1.2s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); transform-origin: top center; } .fa-jello { animation-name: fa-jello; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.9s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); } @media (prefers-reduced-motion: reduce) { .fa-beat, .fa-bounce, .fa-fade, .fa-beat-fade, .fa-flip, .fa-flip-360, .fa-pulse, .fa-shake, .fa-spin, .fa-spin-pulse, .fa-buzz, .fa-float, .fa-jello, .fa-spin-snap, .fa-spin-snap-4, .fa-spin-snap-8, .fa-swing, .fa-wag { animation: none !important; transition: none !important; } } @keyframes fa-beat { 0% { transform: scale(1); } 25% { transform: scale(calc(1.25 * var(--fa-beat-scale, 1.25))); } 45% { transform: scale(calc(1.22 * var(--fa-beat-scale, 1.22))); } 65% { transform: scale(calc(1.25 * var(--fa-beat-scale, 1.25))); } 90% { transform: scale(1); } } @keyframes fa-bounce { 0% { transform: scale(1, 1) translateY(0); animation-timing-function: var(--fa-animation-timing); } 14% { transform: scale(var(--fa-bounce-start-scale-x, 1.06), var(--fa-bounce-start-scale-y, 0.94)) translateY(var(--fa-bounce-anticipation, 3px)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 32% { transform: scale(var(--fa-bounce-jump-scale-x, 0.94), var(--fa-bounce-jump-scale-y, 1.12)) translateY(calc(-1 * var(--fa-bounce-height, 0.5em))); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 52% { transform: scale(1, 1) translateY(calc(-1 * var(--fa-bounce-height, 0.5em) * 1.1)); animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); } 70% { transform: scale(var(--fa-bounce-land-scale-x, 1.06), var(--fa-bounce-land-scale-y, 0.92)) translateY(0); animation-timing-function: cubic-bezier(0.33, 0.33, 0.66, 1); } 85% { transform: scale(0.98, 1.04) translateY(calc(-2px * var(--fa-bounce-rebound, 1))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: scale(1, 1) translateY(0); } } @keyframes fa-fade { 0% { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 40% { opacity: var(--fa-fade-opacity, 0.4); transform: scale(0.98); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 100% { opacity: 1; transform: scale(1); } } @keyframes fa-beat-fade { 0% { opacity: var(--fa-beat-fade-opacity, 0.4); transform: scale(1); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 25% { opacity: calc(var(--fa-beat-fade-opacity, 0.4) + 0.4); transform: scale(var(--fa-beat-fade-scale, 1.28)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 45% { opacity: 1; transform: scale(var(--fa-beat-fade-scale, 1.25)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 65% { opacity: calc(var(--fa-beat-fade-opacity, 0.4) + 0.4); transform: scale(var(--fa-beat-fade-scale, 1.28)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 100% { opacity: var(--fa-beat-fade-opacity, 0.4); transform: scale(1); } } @keyframes fa-flip { 0% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 8% { transform: perspective(2em) scale(var(--fa-flip-anticipation-scale, 0.95)) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 35% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.6)); animation-timing-function: linear; } 65% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.5)); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 92% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * var(--fa-flip-overshoot, 1.04))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -360deg)); } } @keyframes fa-flip-360 { 0% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 8% { transform: perspective(2em) scale(var(--fa-flip-anticipation-scale, 0.95)) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 50% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.6)); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 80% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * var(--fa-flip-overshoot, 1.04))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -360deg)); } } @keyframes fa-shake { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 8% { transform: rotate(35deg) translateX(1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 20% { transform: rotate(-22deg) translateX(-1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 35% { transform: rotate(15deg) translateX(1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 50% { transform: rotate(-9deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 65% { transform: rotate(5deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 78% { transform: rotate(-3deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 90% { transform: rotate(1deg); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 100% { transform: rotate(0deg); } } @keyframes fa-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 12% { transform: rotate(60deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 16.67% { transform: rotate(60deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 28.67% { transform: rotate(120deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 33.33% { transform: rotate(120deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 45.33% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 62% { transform: rotate(240deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 66.67% { transform: rotate(240deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 78.67% { transform: rotate(300deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 83.33% { transform: rotate(300deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 95.33% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap-4 { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 15% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 25% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 40% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 65% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 75% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 90% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap-8 { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 9% { transform: rotate(45deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 12.5% { transform: rotate(45deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 21.5% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 25% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 34% { transform: rotate(135deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 37.5% { transform: rotate(135deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 46.5% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 59% { transform: rotate(225deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 62.5% { transform: rotate(225deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 71.5% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 75% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 84% { transform: rotate(315deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 87.5% { transform: rotate(315deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 96.5% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-buzz { 0% { transform: translateX(0) rotate(0deg); animation-timing-function: cubic-bezier(0.1, 0, 0.9, 1); } 5% { transform: translateX(var(--fa-buzz-distance, 4px)) rotate(0.5deg); } 10% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px))) rotate(-0.5deg); } 15% { transform: translateX(var(--fa-buzz-distance, 4px)) rotate(0.3deg); } 20% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px))) rotate(-0.3deg); } 25% { transform: translateX(calc(var(--fa-buzz-distance, 4px) * 0.7)) rotate(0.2deg); } 30% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px) * 0.7)) rotate(-0.2deg); } 35% { transform: translateX(calc(var(--fa-buzz-distance, 4px) * 0.4)) rotate(0.1deg); } 40% { transform: translateX(0) rotate(0deg); } 100% { transform: translateX(0) rotate(0deg); } } @keyframes fa-wag { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 12% { transform: rotate(var(--fa-wag-angle, 12deg)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 24% { transform: rotate(2deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 36% { transform: rotate(calc(var(--fa-wag-angle, 12deg) * 0.85)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 48% { transform: rotate(1deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 58% { transform: rotate(calc(var(--fa-wag-angle, 12deg) * 0.6)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 68% { transform: rotate(0deg); } 100% { transform: rotate(0deg); } } @keyframes fa-float { 0% { transform: translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 15% { transform: translateY(calc(-0.4 * var(--fa-float-height, 6px))) translateX(var(--fa-float-drift, 1px)) rotate(var(--fa-float-tilt, 1deg)) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 35% { transform: translateY(calc(-1 * var(--fa-float-height, 6px))) translateX(0) rotate(0deg) scale(var(--fa-float-stretch-x, 0.98), var(--fa-float-stretch-y, 1.03)); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 0); } 50% { transform: translateY(calc(-0.92 * var(--fa-float-height, 6px))) translateX(calc(-0.5 * var(--fa-float-drift, 1px))) rotate(calc(-0.5 * var(--fa-float-tilt, 1deg))) scale(0.995, 1.01); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 70% { transform: translateY(calc(-0.3 * var(--fa-float-height, 6px))) translateX(calc(-1 * var(--fa-float-drift, 1px))) rotate(calc(-1 * var(--fa-float-tilt, 1deg))) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 90% { transform: translateY(calc(0.05 * var(--fa-float-height, 6px))) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); } } @keyframes fa-swing { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 8% { transform: rotate(var(--fa-swing-angle, 22deg)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 18% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.85)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 28% { transform: rotate(calc(var(--fa-swing-angle, 22deg) * 0.65)); animation-timing-function: cubic-bezier(0.35, 0, 0.65, 1); } 38% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.45)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 48% { transform: rotate(calc(var(--fa-swing-angle, 22deg) * 0.25)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 56% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.1)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 64% { transform: rotate(0deg); } 100% { transform: rotate(0deg); } } @keyframes fa-jello { 0% { transform: scale(1, 1); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 12% { transform: scale(var(--fa-jello-scale-x, 1.15), calc(2 - var(--fa-jello-scale-x, 1.15))); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 24% { transform: scale(calc(2 - var(--fa-jello-scale-y, 1.12)), var(--fa-jello-scale-y, 1.12)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 36% { transform: scale(calc(1 + (var(--fa-jello-scale-x, 1.15) - 1) * 0.5), calc(2 - (1 + (var(--fa-jello-scale-x, 1.15) - 1) * 0.5))); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 48% { transform: scale(calc(2 - (1 + (var(--fa-jello-scale-y, 1.12) - 1) * 0.3)), calc(1 + (var(--fa-jello-scale-y, 1.12) - 1) * 0.3)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 58% { transform: scale(1.02, 0.98); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 68% { transform: scale(1, 1); } 100% { transform: scale(1, 1); } } 各皮アニメヌションデモ Beat (錓動) /* * 共通蚭定 */ .material-symbols-outlined[class*="fa-"] { display: inline-block; line-height: 1; font-size: 52px; padding: 16px; color:oklch(37.2% 0.044 257.287); } /* * 各アニメヌション */ .fa-beat { animation-name: fa-beat; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-bounce { animation-name: fa-bounce; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, cubic-bezier(0.28, 0.84, 0.42, 1)); } .fa-fade { animation-name: fa-fade; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-beat-fade { animation-name: fa-beat-fade; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-flip { animation-name: fa-flip; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1.5s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-flip-360 { animation-name: fa-flip-360; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-shake { animation-name: fa-shake; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.75s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); } .fa-spin { animation-name: fa-spin; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 2s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-reverse { --fa-animation-direction: reverse; } .fa-pulse, .fa-spin-pulse { animation-name: fa-spin; animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, steps(8)); } .fa-spin-snap { animation-name: fa-spin-snap; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 3s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-snap-4 { animation-name: fa-spin-snap-4; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 2.4s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-spin-snap-8 { animation-name: fa-spin-snap-8; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 4s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-buzz { animation-name: fa-buzz; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.6s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, linear); } .fa-wag { animation-name: fa-wag; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.9s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); transform-origin: bottom center; } .fa-float { animation-name: fa-float; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 3s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-in-out); will-change: transform; } .fa-swing { animation-name: fa-swing; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 1.2s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); transform-origin: top center; } .fa-jello { animation-name: fa-jello; animation-delay: var(--fa-animation-delay, 0s); animation-direction: var(--fa-animation-direction, normal); animation-duration: var(--fa-animation-duration, 0.9s); animation-iteration-count: var(--fa-animation-iteration-count, infinite); animation-timing-function: var(--fa-animation-timing, ease-out); } @media (prefers-reduced-motion: reduce) { .fa-beat, .fa-bounce, .fa-fade, .fa-beat-fade, .fa-flip, .fa-flip-360, .fa-pulse, .fa-shake, .fa-spin, .fa-spin-pulse, .fa-buzz, .fa-float, .fa-jello, .fa-spin-snap, .fa-spin-snap-4, .fa-spin-snap-8, .fa-swing, .fa-wag { animation: none !important; transition: none !important; } } @keyframes fa-beat { 0% { transform: scale(1); } 25% { transform: scale(calc(1.25 * var(--fa-beat-scale, 1.25))); } 45% { transform: scale(calc(1.22 * var(--fa-beat-scale, 1.22))); } 65% { transform: scale(calc(1.25 * var(--fa-beat-scale, 1.25))); } 90% { transform: scale(1); } } @keyframes fa-bounce { 0% { transform: scale(1, 1) translateY(0); animation-timing-function: var(--fa-animation-timing); } 14% { transform: scale(var(--fa-bounce-start-scale-x, 1.06), var(--fa-bounce-start-scale-y, 0.94)) translateY(var(--fa-bounce-anticipation, 3px)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 32% { transform: scale(var(--fa-bounce-jump-scale-x, 0.94), var(--fa-bounce-jump-scale-y, 1.12)) translateY(calc(-1 * var(--fa-bounce-height, 0.5em))); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 52% { transform: scale(1, 1) translateY(calc(-1 * var(--fa-bounce-height, 0.5em) * 1.1)); animation-timing-function: cubic-bezier(0.5, 0, 1, 0.5); } 70% { transform: scale(var(--fa-bounce-land-scale-x, 1.06), var(--fa-bounce-land-scale-y, 0.92)) translateY(0); animation-timing-function: cubic-bezier(0.33, 0.33, 0.66, 1); } 85% { transform: scale(0.98, 1.04) translateY(calc(-2px * var(--fa-bounce-rebound, 1))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: scale(1, 1) translateY(0); } } @keyframes fa-fade { 0% { opacity: 1; transform: scale(1); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 40% { opacity: var(--fa-fade-opacity, 0.4); transform: scale(0.98); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 100% { opacity: 1; transform: scale(1); } } @keyframes fa-beat-fade { 0% { opacity: var(--fa-beat-fade-opacity, 0.4); transform: scale(1); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 25% { opacity: calc(var(--fa-beat-fade-opacity, 0.4) + 0.4); transform: scale(var(--fa-beat-fade-scale, 1.28)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 45% { opacity: 1; transform: scale(var(--fa-beat-fade-scale, 1.25)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 65% { opacity: calc(var(--fa-beat-fade-opacity, 0.4) + 0.4); transform: scale(var(--fa-beat-fade-scale, 1.28)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 100% { opacity: var(--fa-beat-fade-opacity, 0.4); transform: scale(1); } } @keyframes fa-flip { 0% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 8% { transform: perspective(2em) scale(var(--fa-flip-anticipation-scale, 0.95)) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 35% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.6)); animation-timing-function: linear; } 65% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.5)); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 92% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * var(--fa-flip-overshoot, 1.04))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -360deg)); } } @keyframes fa-flip-360 { 0% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.4, 1); } 8% { transform: perspective(2em) scale(var(--fa-flip-anticipation-scale, 0.95)) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), 0deg); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 50% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * 0.6)); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 80% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), calc(var(--fa-flip-angle, -360deg) * var(--fa-flip-overshoot, 1.04))); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: perspective(2em) scale(1) rotate3d(var(--fa-flip-x, 0), var(--fa-flip-y, 1), var(--fa-flip-z, 0), var(--fa-flip-angle, -360deg)); } } @keyframes fa-shake { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 8% { transform: rotate(35deg) translateX(1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 20% { transform: rotate(-22deg) translateX(-1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 35% { transform: rotate(15deg) translateX(1px); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 50% { transform: rotate(-9deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 65% { transform: rotate(5deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 78% { transform: rotate(-3deg); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 90% { transform: rotate(1deg); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 100% { transform: rotate(0deg); } } @keyframes fa-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 12% { transform: rotate(60deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 16.67% { transform: rotate(60deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 28.67% { transform: rotate(120deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 33.33% { transform: rotate(120deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 45.33% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 62% { transform: rotate(240deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 66.67% { transform: rotate(240deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 78.67% { transform: rotate(300deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 83.33% { transform: rotate(300deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 95.33% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap-4 { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 15% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 25% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 40% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 65% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 75% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 90% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-spin-snap-8 { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 9% { transform: rotate(45deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 12.5% { transform: rotate(45deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 21.5% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 25% { transform: rotate(90deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 34% { transform: rotate(135deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 37.5% { transform: rotate(135deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 46.5% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 50% { transform: rotate(180deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 59% { transform: rotate(225deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 62.5% { transform: rotate(225deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 71.5% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 75% { transform: rotate(270deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 84% { transform: rotate(315deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 87.5% { transform: rotate(315deg); animation-timing-function: cubic-bezier(0, 0, 0.2, 1); } 96.5% { transform: rotate(360deg); animation-timing-function: cubic-bezier(0.8, 0, 1, 1); } 100% { transform: rotate(360deg); } } @keyframes fa-buzz { 0% { transform: translateX(0) rotate(0deg); animation-timing-function: cubic-bezier(0.1, 0, 0.9, 1); } 5% { transform: translateX(var(--fa-buzz-distance, 4px)) rotate(0.5deg); } 10% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px))) rotate(-0.5deg); } 15% { transform: translateX(var(--fa-buzz-distance, 4px)) rotate(0.3deg); } 20% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px))) rotate(-0.3deg); } 25% { transform: translateX(calc(var(--fa-buzz-distance, 4px) * 0.7)) rotate(0.2deg); } 30% { transform: translateX(calc(-1 * var(--fa-buzz-distance, 4px) * 0.7)) rotate(-0.2deg); } 35% { transform: translateX(calc(var(--fa-buzz-distance, 4px) * 0.4)) rotate(0.1deg); } 40% { transform: translateX(0) rotate(0deg); } 100% { transform: translateX(0) rotate(0deg); } } @keyframes fa-wag { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 12% { transform: rotate(var(--fa-wag-angle, 12deg)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 24% { transform: rotate(2deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 36% { transform: rotate(calc(var(--fa-wag-angle, 12deg) * 0.85)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 48% { transform: rotate(1deg); animation-timing-function: cubic-bezier(0.2, 0, 0.6, 1); } 58% { transform: rotate(calc(var(--fa-wag-angle, 12deg) * 0.6)); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 68% { transform: rotate(0deg); } 100% { transform: rotate(0deg); } } @keyframes fa-float { 0% { transform: translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 15% { transform: translateY(calc(-0.4 * var(--fa-float-height, 6px))) translateX(var(--fa-float-drift, 1px)) rotate(var(--fa-float-tilt, 1deg)) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 35% { transform: translateY(calc(-1 * var(--fa-float-height, 6px))) translateX(0) rotate(0deg) scale(var(--fa-float-stretch-x, 0.98), var(--fa-float-stretch-y, 1.03)); animation-timing-function: cubic-bezier(0.5, 0, 0.5, 0); } 50% { transform: translateY(calc(-0.92 * var(--fa-float-height, 6px))) translateX(calc(-0.5 * var(--fa-float-drift, 1px))) rotate(calc(-0.5 * var(--fa-float-tilt, 1deg))) scale(0.995, 1.01); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 0.33); } 70% { transform: translateY(calc(-0.3 * var(--fa-float-height, 6px))) translateX(calc(-1 * var(--fa-float-drift, 1px))) rotate(calc(-1 * var(--fa-float-tilt, 1deg))) scale(1, 1); animation-timing-function: cubic-bezier(0.33, 0.66, 0.66, 1); } 90% { transform: translateY(calc(0.05 * var(--fa-float-height, 6px))) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); animation-timing-function: cubic-bezier(0.33, 0, 0.66, 1); } 100% { transform: translateY(0) translateX(0) rotate(0deg) scale(var(--fa-float-squash-x, 1.02), var(--fa-float-squash-y, 0.98)); } } @keyframes fa-swing { 0% { transform: rotate(0deg); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 8% { transform: rotate(var(--fa-swing-angle, 22deg)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 18% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.85)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 28% { transform: rotate(calc(var(--fa-swing-angle, 22deg) * 0.65)); animation-timing-function: cubic-bezier(0.35, 0, 0.65, 1); } 38% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.45)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 48% { transform: rotate(calc(var(--fa-swing-angle, 22deg) * 0.25)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 56% { transform: rotate(calc(-1 * var(--fa-swing-angle, 22deg) * 0.1)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 64% { transform: rotate(0deg); } 100% { transform: rotate(0deg); } } @keyframes fa-jello { 0% { transform: scale(1, 1); animation-timing-function: cubic-bezier(0.2, 0, 0.8, 1); } 12% { transform: scale(var(--fa-jello-scale-x, 1.15), calc(2 - var(--fa-jello-scale-x, 1.15))); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 24% { transform: scale(calc(2 - var(--fa-jello-scale-y, 1.12)), var(--fa-jello-scale-y, 1.12)); animation-timing-function: cubic-bezier(0.3, 0, 0.7, 1); } 36% { transform: scale(calc(1 + (var(--fa-jello-scale-x, 1.15) - 1) * 0.5), calc(2 - (1 + (var(--fa-jello-scale-x, 1.15) - 1) * 0.5))); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 48% { transform: scale(calc(2 - (1 + (var(--fa-jello-scale-y, 1.12) - 1) * 0.3)), calc(1 + (var(--fa-jello-scale-y, 1.12) - 1) * 0.3)); animation-timing-function: cubic-bezier(0.4, 0, 0.6, 1); } 58% { transform: scale(1.02, 0.98); animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); } 68% { transform: scale(1, 1); } 100% { transform: scale(1, 1); } } favorite add_circle zoom_out_map pan_zoom fingerprint bolt mode_heat <span class="material-symbols-outlined ms-beat">favorite</span> <span class="material-symbols-outlined ms-beat">add_circle</span> <span class="material-symbols-outlined ms-beat">zoom_out_map</span> <span class="material-symbols-outlined ms-beat">pan_zoom</span> <span class="material-symbols-outlined ms-beat">fingerprint</span> <span class="material-symbols-outlined ms-beat" style="--ms-beat-scale: 2.0;">bolt</span> <span class="material-symbols-outlined ms-beat" style="--ms-animation-duration: 0.5s;">mode_heat</span> Fade (フェヌド) warning timer motion_blur battery_1_bar diamond_shine bomb footprint <span class="material-symbols-outlined fa-fade">warning</span> <span class="material-symbols-outlined fa-fade">timer</span> <span class="material-symbols-outlined fa-fade" style="--fa-fade-opacity: 0;">motion_blur</span> <span class="material-symbols-outlined fa-fade">battery_1_bar</span> <span class="material-symbols-outlined fa-fade">diamond_shine</span> <span class="material-symbols-outlined fa-fade" style="--fa-animation-duration: 1s;">bomb</span> <span class="material-symbols-outlined fa-fade">footprint</span> Beat-Fade (錓動、フェヌド) info release_alert speaker_2 trophy award_meal editor_choice celebration <span class="material-symbols-outlined fa-beat-fade">info</span> <span class="material-symbols-outlined fa-beat-fade">release_alert</span> <span class="material-symbols-outlined fa-beat-fade">speaker_2</span> <span class="material-symbols-outlined fa-beat-fade">trophy</span> <span class="material-symbols-outlined fa-beat-fade">award_meal</span> <span class="material-symbols-outlined fa-beat-fade">editor_choice</span> <span class="material-symbols-outlined fa-beat-fade" style="--fa-beat-fade-opacity: 0.1; --fa-beat-fade-scale: 2;">celebration</span> Bounce (バりンド) sports_volleyball sports_basketball mail chat cruelty_free skateboarding kitesurfing <span class="material-symbols-outlined fa-bounce">sports_volleyball</span> <span class="material-symbols-outlined fa-bounce" style="--fa-bounce-land-scale-x: 1.2;--fa-bounce-land-scale-y: .8;--fa-bounce-rebound: 5px;">sports_basketball</span> <span class="material-symbols-outlined fa-bounce" style="--fa-bounce-start-scale-x: 1;--fa-bounce-start-scale-y: 1;--fa-bounce-jump-scale-x: 1;--fa-bounce-jump-scale-y: 1;--fa-bounce-land-scale-x: 1;--fa-bounce-land-scale-y: 1;--fa-bounce-rebound: 0;">mail</span> <span class="material-symbols-outlined fa-bounce" style="--fa-bounce-start-scale-x: 1;--fa-bounce-start-scale-y: 1;--fa-bounce-jump-scale-x: 1;--fa-bounce-jump-scale-y: 1;--fa-bounce-land-scale-x: 1;--fa-bounce-land-scale-y: 1;--fa-bounce-rebound: 0;">chat</span> <span class="material-symbols-outlined fa-bounce" style="--fa-bounce-start-scale-x: 1; --fa-bounce-start-scale-y: 1; --fa-bounce-jump-scale-x: 1; --fa-bounce-jump-scale-y: 1; --fa-bounce-land-scale-x: 1; --fa-bounce-land-scale-y: 1;">cruelty_free</span> <span class="material-symbols-outlined fa-bounce" style="--fa-animation-duration: 2s;">skateboarding</span> <span class="material-symbols-outlined fa-bounce" style="--fa-animation-duration: 4s;">kitesurfing</span> Buzz (振動) mobile notifications alarm <span class="material-symbols-outlined fa-buzz">mobile</span> <span class="material-symbols-outlined fa-buzz" style="--fa-animation-duration: 2s; --fa-buzz-distance: 9px;">notifications</span> <span class="material-symbols-outlined fa-buzz">alarm</span> Flip (裏返し 180床) poker_chip docs send album cannabis award_star verified <span class="material-symbols-outlined fa-flip">poker_chip</span> <span class="material-symbols-outlined fa-flip">docs</span> <span class="material-symbols-outlined fa-flip" style="--fa-flip-x: 1; --fa-flip-y: 0;">send</span> <span class="material-symbols-outlined fa-flip" style="--fa-flip-x: 1; --fa-flip-y: 1;">album</span> <span class="material-symbols-outlined fa-flip" style="--fa-animation-duration: 3s;">cannabis</span> <span class="material-symbols-outlined fa-flip">award_star</span> <span class="material-symbols-outlined fa-flip">verified</span> Flip-360 (裏返し 360床) poker_chip docs send album cannabis award_star verified <span class="material-symbols-outlined fa-flip-360">poker_chip</span> <span class="material-symbols-outlined fa-flip-360">docs</span> <span class="material-symbols-outlined fa-flip" style="--fa-flip-x: 1; --fa-flip-y: 0;">send</span> <span class="material-symbols-outlined fa-flip-360" style="--fa-flip-x: 1; --fa-flip-y: 1;">album</span> <span class="material-symbols-outlined fa-flip-360" style="--fa-animation-duration: 3s;">cannabis</span> <span class="material-symbols-outlined fa-flip-360">award_star</span> <span class="material-symbols-outlined fa-flip-360">verified</span> Float (浮遊) cloud tooltip bubble_chart hot_tub sailing flight_takeoff <span class="material-symbols-outlined fa-float">cloud</span> <span class="material-symbols-outlined fa-float">tooltip</span> <span class="material-symbols-outlined fa-float" style="--fa-float-height: .5em;">bubble_chart</span> <span class="material-symbols-outlined fa-float">hot_tub</span> <span class="material-symbols-outlined fa-float" style="--fa-animation-duration: 2s;">sailing</span> <span class="material-symbols-outlined fa-float">flight_takeoff</span> Jello (れリヌのような揺れ) water_drop kid_star deployed_code support <span class="material-symbols-outlined fa-jello">water_drop</span> <span class="material-symbols-outlined fa-jello">kid_star</span> <span class="material-symbols-outlined fa-jello" style="--fa-jello-scale-x: 1.5;">deployed_code</span> <span class="material-symbols-outlined fa-jello" style="--fa-animation-duration: 2s;">support</span> Shake (振る) notifications timer lock science casino <span class="material-symbols-outlined fa-shake">notifications</span> <span class="material-symbols-outlined fa-shake">timer</span> <span class="material-symbols-outlined fa-shake">lock</span> <span class="material-symbols-outlined fa-shake">science</span> <span class="material-symbols-outlined fa-shake">casino</span> Spin (回転) cycle autorenew progress_activity settings star brightness_empty avg_pace workspaces settings_backup_restore mobile_rotate change_circle <span class="material-symbols-outlined fa-spin">cycle</span> <span class="material-symbols-outlined fa-spin">autorenew</span> <span class="material-symbols-outlined fa-spin">progress_activity</span> <span class="material-symbols-outlined fa-spin-snap">settings</span> <span class="material-symbols-outlined fa-spin">star</span> <span class="material-symbols-outlined fa-spin-snap-8">brightness_empty</span> <span class="material-symbols-outlined fa-spin-pulse">avg_pace</span> <span class="material-symbols-outlined fa-spin">workspaces</span> <span class="material-symbols-outlined fa-spin fa-spin-reverse">settings_backup_restore</span> <span class="material-symbols-outlined fa-spin fa-spin-reverse">mobile_rotate</span> <span class="material-symbols-outlined fa-spin fa-spin-reverse">change_circle</span> Swing (ぶら䞋がり揺れ) lock_open content_paste key_vertical notifications <span class="material-symbols-outlined fa-swing">lock_open</span> <span class="material-symbols-outlined fa-swing" style="--fa-animation-duration: 2s;">content_paste</span> <span class="material-symbols-outlined fa-swing">key_vertical</span> <span class="material-symbols-outlined fa-swing" style="--fa-swing-angle: 45deg;">notifications</span> Wag (しっぜ振り) water_full local_laundry_service pets balance music_note <span class="material-symbols-outlined fa-wag">water_full</span> <span class="material-symbols-outlined fa-wag">local_laundry_service</span> <span class="material-symbols-outlined fa-wag">pets</span> <span class="material-symbols-outlined fa-wag" style="--fa-animation-duration: 2s;">balance</span> <span class="material-symbols-outlined fa-wag" style="--fa-wag-angle: 45deg;">music_note</span> 感想 実際のりェブサむトやアプリケヌションに組み蟌む堎合は、アニメヌションの倚甚は犁物で、䜿いどころを芋極める必芁があるず思いたした。動き続ける芁玠は目障りだったり、アクセシビリティが確保されない堎合がありたす。 装食ずしおアニメヌションさせる堎合は停止ボタンを蚭けたり、ボタンの操䜜に察しおのみ動きを぀ける、ルヌプの回数を制限するなど、配慮が必芁です。 読み蟌み䞭を瀺す動くアむコンは状況を分かりやすくしおくれたすし、泚意を促したり、次のアクションを導くための動きは実甚的です。ここぞずいうずころで、動くアむコンを組み蟌みたいず思いたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Material Symbols & IconsにFont Awesomeのアニメヌションを適甚しおみた first appeared on SIOS Tech Lab .
サむオステクノロゞヌは、OSS管理ツヌル「SCANOSS」の 日本囜内初の代理店 です。 生成AIでコヌドを曞く比重が䞊がるに぀れ、「動くコヌドは手に入ったが、それを䜿っおいいか」を確かめる工皋が抜け萜ちるようになりたした。テストは通る。脆匱性スキャンを回しおいれば、それも通る。しかし、そのコヌドがどこ由来なのかを芋る工皋は、どちらにも入っおいたせん。 AIが生成したコヌドに含たれるOSSは、読んで芋分けるこずができたせん 。機械的な照合で芋える範囲ず、機械にも芋えない範囲が残りたす。 この蚘事でわかるこず : 生成されたコヌドの出所を、読んで芋分けられない理由 OSSが混ざっおいた堎合に、ラむセンス皮別ごずに䜕が求められるか 䜕を芋るツヌルがあり、そのうちコヌドを芋るものはどれか 機械的な照合でも刀定できない範囲 このコヌドにOSSは入っおいるか 次のコヌドを読んでください。JavaScriptで、文字列をUTF-8のバむト列に倉換する凊理です。 // convert string to array (typed, when possible) exports.string2buf = function (str) { var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0; // count binary size for (m_pos = 0; m_pos < str_len; m_pos++) { c = str.charCodeAt(m_pos); if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) { c2 = str.charCodeAt(m_pos + 1); if ((c2 & 0xfc00) === 0xdc00) { c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00); m_pos++; } } buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4; } //  UTF-8バむト列ぞの曞き蟌みは䞭略  return buf; }; このコヌドにOSS由来のものが混ざっおいるか、読んで刀定できたでしょうか。 答えは、 このコヌド自䜓がOSS です。圧瞮ラむブラリ pako v1.0.11 の lib/utils/strings.js からの抜粋MITで、埌半のバむト列ぞの曞き蟌みは省略しおありたす。 pako — Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn / MIT License 芋分けられなかったずしおも、泚意力の問題ではありたせん。理由は2぀ありたす。 照合すべきOSSが倚すぎる 1぀目は単玔な話です。䞖に公開されおいるOSSのコヌドすべおず、目の前のコヌドを突き合わせる䜜業になりたす。人間の蚘憶で照合できる芏暡ではありたせん。 そしお、これは「珍しく起きるこず」でもありたせん。ICSE 2025で発衚された LiCoEval は、14のLLMに4,187件のコヌドを生成させ、既存のOSS実装ず匷く䌌おいるものがどれだけ含たれるかを枬っおいたす。コヌド生成の性胜が高い3モデルGPT-4o / Claude 3.5 Sonnet / DeepSeek-Coder-V2では、生成したコヌドのうち 0.88%〜2.01% が該圓したした。14モデル党䜓で芋るず 0.0%〜2.17% たで幅があり、最倚はコヌド生成の性胜で䞋䜍のCodestral2.17%、0件だったのは性胜が䞭䜍のGLM-4-9B-Chatでした。なお、この枬定の察象は2024幎時点のモデル矀です。モデルは入れ替わるので、この数倀がそのたた今のモデルに圓おはたるわけではありたせん。 数字だけ芋るず小さく感じたすが、 この倀は䞋限 です。論文自身が枬定の限界ずしおこう曞いおいたす。 Our striking similarity standard focuses on precision, potentially overlooking cases where LLMs generate code derived from open-source code but fall below our threshold この刀定基準は粟床を重芖しおいるため、OSS由来のコヌドを生成しおいおも閟倀を䞋回るケヌスは芋萜ずす可胜性がある ぀たり、実際にはこれより倚く起きおいる可胜性がありたす。 出力に出所の情報が付かない 2぀目のほうが厄介です。人がOSSを持っおくるずきは、由来を知っおいお、倚くの堎合ラむセンスファむルも䞀緒に付いおきたす。生成された堎合は、 䜕も付いおきたせん 。 同じLiCoEvalは、モデルが生成したコヌドに぀いお、ラむセンス情報を正しく瀺せたかも枬っおいたす。コピヌレフトラむセンス次章で芋るずおり、矩務がもっずも重い皮別のコヌドで出所を正しく瀺せた割合は、GPT-4o・GPT-3.5 Turbo・GPT-4 Turbo・Gemini 1.5 Pro・DeepSeek-Coder-V2・Codestral のいずれも 0.0 。Claude 3.5 Sonnet の 0.4 が唯䞀の䟋倖でした。 「性胜の高いモデルを䜿えば避けられる」ずいう話でもありたせん。0.0 が䞊んでいるモデルには、コヌド生成の性胜で䞊䜍3モデルに入る GPT-4o ず DeepSeek-Coder-V2 が含たれおいたす。論文自身も、この皮のスコアず生成性胜が察応しないこずに泚意を促しおいたす。 a high LICO score, particularly a score of 1 in the absence of any strikingly similar cases, is not meaningful if the model’s code generation performance is poor. Models producing erroneous or chaotic code may naturally avoid striking similarities コヌド生成の性胜が䜎いモデルでは、高いスコアずくに䌌た事䟋が0件で満点になる堎合は意味を持たない。誀ったコヌドや混乱したコヌドを出すモデルは、そもそも匷い類䌌を避けおしたう ぀たりこの手のスコアは、性胜が䜎いモデルほど良く芋える向きに歪みたす。 モデルを遞び盎しお解決する問題ではありたせん 。 OSSが混ざっおいたら、䜕をしないずいけないのか 「AIが出力したコヌドの著䜜暩をめぐる争いはただ決着しおいないのだから、様子を芋ればいい」ず考えるこずもできたす。実際、その論点に倖から答えは出おいたせん。 Copilotの出力をめぐる集団蚎蚟 Doe v. GitHub は、22件の請求のうち20件が地裁で华䞋され、契玄違反ずOSSラむセンス違反の2件が係属䞭です。华䞋された著䜜暩管理情報に関する請求は控蚎され、2026幎2月11日に第9巡回区で口頭匁論が行われ、刀断を埅っおいる状態です2026幎9月3日時点。 ただし、 混ざっおいた堎合に䜕が求められるかは、AIずは関係なく、すでに明文で決たっおいたす 。 皮別 代衚䟋 コヌドに取り蟌んだ堎合に生じるこず permissive MIT / Apache-2.0 / BSD 著䜜暩衚瀺ずラむセンス文の 衚瀺 。矩務がれロではありたせん 匱コピヌレフト LGPL / MPL 衚瀺に加えお、 そのOSS郚分を改倉したなら゜ヌスの開瀺 匷コピヌレフト GPL / AGPL 結合した自瀟のコヌドにたで開瀺矩務が及びうる 。AGPLはネットワヌク越しに䜿わせる堎合にも及びたす 順に芋おいきたす。permissiveは「自由に䜿える」ず理解されがちですが、衚瀺の矩務は残りたす。衚瀺を萜ずせば条件違反です。 匱コピヌレフトは、そのOSS郚分を改倉したかどうかで倉わりたす。改倉しお配垃するなら、その郚分の゜ヌスを開瀺するこずになりたす。 匷コピヌレフトは、圱響範囲が自分のコヌド偎に及びたす。どこたでが「結合」なのかは配垃圢態や結合方法で倉わるため、ここが最も刀断の重い領域です。 ただし、同じ皮別でも個別のラむセンスごずに条件は違いたす。䞊の衚は皮別ごずに䜕が付いおくるかの傟向で、実際にどの矩務が生じるかは、そのラむセンスの条文ず、結合の圢や配垃の方法で決たりたす。どのラむセンスを通すかも、䜿う偎が決めるこずです。 どちらも、どんなラむセンスのものが混ざっおいるかが分かっおからの話 です。 ツヌルは䜕をしおくれるのか 人間が読んで分からないのであれば、機械に照合させるこずになりたす。ただし「OSSラむセンスを芋るツヌル」ずたずめられおいるものは、 䜕を芋おいるかがそれぞれ違いたす 。3぀に分かれたす。 以䞋は 䜕を芋おいる道具なのか の分類で、補品どうしの比范ではありたせん。冒頭に曞いた立堎のずおり、この分類には圓瀟が扱う補品も入りたす。 䜕を芋おいるか 道具 AI生成コヌドの混入 䟝存の宣蚀manifestずlockfile Dependency graph  dependency-review-action  Trivy 映らない ファむルの䞭のラむセンスの蚘述 ScanCode Toolkit ラむセンス文が残っおいれば映る コヌド片の指王 FossID  Black Duck  SCANOSS ここで映る 䞊から順に、芋おいる察象がコヌドの内偎ぞ入っおいきたす。いちばん䞋の指王の照合だけが、宣蚀にもラむセンス文にも珟れないコヌドそのものを芋たす。この局は補品が限られおおり、公匏ドキュメントで機胜を確認できたのは䞊の3぀でした [^1]。 衚に茉せおいない道具にも觊れおおきたす。䟝存しおいるパッケヌゞの問題を知らせる仕組みずしお広く入っおいるのが、GitHubの Dependabotのアラヌト です。 芋おいるのは、衚のいちばん䞊の局です 。公匏ドキュメントを確認した限り、Dependabotのアラヌトは脆匱性の怜出に限定されおいお、ラむセンスを芋る機胜ぞの蚀及がありたせん。 すでに䜕かを回しおいるから、ラむセンスたで芋えおいるずは限りたせん 。 AI生成コヌドの混入が問題になるのは、 衚のいちばん䞋が扱う範囲 です。宣蚀されおいないコヌドは、䟝存の宣蚀を芋るツヌルには映りたせん。 [^1]: 同じ局で公匏ドキュメントに蚘述を確認できたものずしお、他に FOSSA ファむル単䜍の指王照合・確率的なマッチず明蚘ず Revenera Code Insight source-code fingerprintsがありたす。本蚘事は各補品の粟床・速床・䟡栌の比范は行いたせん。 機械に任せれば終わりなのか ここたでで「機械に照合させればよい」ずいう話になりたすが、機械にも芋えない範囲が残りたす。 1぀は、 手が入るほど圓たらなくなる こずです。指王の照合が匷いのは、そのたたの圢に近いコヌドです。曞き盎されたり、別の蚀語ぞ移されたりするほど、照合の手がかりは枛っおいきたす。これは特定の補品の性胜ではなく、 指王を照合するずいう方法そのものの性質 です。どこたで手が入るず倖れるのかは、道具によっお違いたす。 もう1぀は、 䌌おいるこずが由来の蚌明にはならない こずです。LiCoEvalはこう曞いおいたす。 Text similarity alone cannot determine non-independent creation in LLM-generated code. LLMs can produce highly similar code even for unseen samples テキストの類䌌床だけでは、LLMが生成したコヌドが独立創䜜でないずは刀定できない。孊習で芋おいないサンプルに察しおも、LLMは非垞に䌌たコヌドを出せる 䌌おいるコヌドが出おきたずき、それが取り蟌みなのか、独立しお曞かれた結果なのかは、機械の出力だけでは決たりたせん。もちろん機械怜出の補品が100%発芋しおくれるずも蚀えたせん。 ただし、限界があるこずは、機械に照合させない理由にはなりたせん。 比べる盞手は「完璧な怜出」ではなく、䜕も芋おいない状態のほう です。完璧に怜出できる道具が無いのはこの領域に限りたせんが、それでも入れるのは、入れない堎合ずの差が倧きいからです。 分担で蚀うず、 機械が担うのは怜出たで です。出おきたものが本圓に取り蟌みなのかを決めるのは人の偎に残りたす。この分担を先に持っおおくず、ツヌルの出力を「答え」ではなく「候補」ずしお受け取れたす。 手元で動かしおみる堎合は、SCANOSS の CLI でロヌカルスキャンを詊す手順を 別の蚘事 にたずめおいたす。むンストヌルからスキャン結果の読み方、SBOM の生成たでを扱っおいたす。 なぜこの確認が芁るのか AIに曞かせたコヌドは、スキャンにかけお刀断するしかありたせん。 読んで芋分けられない以䞊、出所を確かめる方法がほかに無いからです。 冒頭のコヌドに戻りたす。あれがpako由来だず芋分けられなかったのは、照合すべきOSSが倚すぎるうえに、生成されたコヌドには出所が付いおこないからでした。人の目では、どちらも埋められたせん。曞かれる量が増えおも、埋たるようにはなりたせん。 そしお、 確かめおいないこずは、混ざっおいないこずではありたせん 。混ざっおいた堎合に生じる矩務は、すでに決たっおいたす。スキャンしおいないコヌドに぀いお蚀えるのは「混ざっおいない」ではなく、「 混ざっおいるかどうかを蚀えない 」だけです。生成の速床が䞊がるほど、その状態のコヌドが増えおいきたす。 機械が出せるのは候補たでで、そこから先は人が刀断したす。それでも、候補が出おこなければ刀断のしようがありたせん。 自分のコヌドに䜕が入っおいるかは、倖の答えを埅たなくおも、確かめれば分かりたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 1人がこの投皿は圹に立ったず蚀っおいたす。 The post AI生成コヌドのOSSラむセンス自動スキャンが必芁な理由 first appeared on SIOS Tech Lab .
セッション、こういうずきどうしおたすか セッションを動かしおるず、こういう瞬間っおありたせんか。 タスクの最䞭に「これもやりたい」っお思い぀いたずき 「なんか AI の返答が荒れたぞ」っお思ったずき 䞊列でセッションを回しおお、頭がパンクしおるずき 䜜業がひず段萜しお、「よし、ここたではできた」っおなる。でもただコミットは打おない段階。たぁセッションが長くなったら性胜が萜ちるっお話はもうだいぶ有名ですよね。 このずきに僕がやっおいるのは、AI にドキュメントを䜜らせるこずです。次のセッションぞの 発泚曞 ですね。そこに人間の監査を入れお質を䞊げるずいうのが今回のお話です。 これ、チャットで新しい䌚話を始める人にも同じこずは起きるず思うんですが、そっちは Geminiの答えが雑になっおきた人ぞ。䌚話は䜿い捚おでいいんです に曞きたした。ここでは Claude Code の話に絞りたす。 セヌブポむントっお、もう機胜でありたすよね 匕き継ぎのドキュメントず蚀われるず、セヌブポむントの機胜がもうあるじゃないかず思う方もいるず思いたす。たしかにその通りで、Claude Code には正匏に checkpointing ずいう機胜がありたす。しかもこれ、プロンプトを送るたびに勝手に䜜られおるんですよね100個たで保持されお、30日で消えたす。 セヌブポむントっぜい操䜜、実は3぀ありたす。 /resume セッションをたたいで、䞭断したずころを䞀括で埩垰する /rewind 同䞀セッション内。地点を遞んで戻せたす。遞べるのは5択コヌドず䌚話䌚話だけコヌドだけここから先を芁玄ここより前を芁玄 /compact 同䞀セッション内。䌚話を構造化サマリヌに眮き換えたす。 /compact focus on 〜 で焊点を指瀺するこずもできたす 実際に1ヶ月ぶんの履歎を数えおみたら、 /clear が221回でした。それに察しお /rewind は0回、 /compact も0回。匕き継ぎドキュメントを䜜ったのは18回です。基本的に1セッションに収たるだけのタスクに切り分けられおいる感じですね ただし /resume だけは、VSCode が萜ちたずきの埩旧の際に䜿っおいたす。 前提ずしお /rewind ず /compact は同䞀セッション内でしか䜿えない操䜜です。遞べるのは範囲か焊点たでなんですよね。どれを残すかを自分で決める工皋がない。 /rewind は「い぀」しか遞べないし、 /compact は焊点を指瀺できおも、実際に曞くのは AI です。実際にセッション内でどう解釈されたのかずいったこずを確認するこずができないんです。なので、セッション内でAIずの間に誀解が生たれたらそれを気づくこずができないっお感じですね。 /resume は党郚持っおいきたす。これはセッションをあずからAIに分析させるむメヌゞですね。これをそのたた䜿うのもありなんですが、トヌクン効率的に極力やりたくはないですね。 /rewind はガチのセヌブポむントですね。特定の起点にタグを打っお、たたそこから再スタヌトするみたいなこずができたす。 /compact のほうは、AI 自身が䌚話を圧瞮するのが課題です。AI の出力が荒れおいるずしお、荒れた人が圧瞮したらその圧瞮を信甚しおいいのかっおなりたすよね。 ただ3぀䞊べおみるず、これ党郚「 過去をどう残すか 」の道具になりたす。地点たで戻す、芁玄しお残す、䞞ごず運ぶ。向きが党郚うしろです。 発泚曞は逆で、䜕をやったかじゃなくお次に䜕をやるかが曞いおある玙です。0回なのもたぶんそういうこずで、機胜が足りないんじゃなくお、僕が欲しかったものが保存じゃなかった。 AIに指瀺をたずめた発泚曞を䜜らせる 発泚曞だず思うず、わざわざドキュメントにしおいる理由もはっきりしたす。セヌブデヌタっお、人間は読たないんですよ。埩元する偎が読むものなので。でも発泚曞は、出した人が読み返しお「あ、これ違う」っお盎すものですよね。䌚話の䞭に埋たっおいるうちは、自分がどう解釈されたのかを確かめられたせん。ドキュメントになるず読み返せたす。読み返せるから、AI ずの間にできた誀解をこっちが芋぀けお盎せたす。 そもそも、い぀枡すのか 匕き継ぎを䜜るのは䌚話が重くなっおからだず思われがちなんですが、人間の郜合にも䜿えたす。 物理的に䞭断されるずき移動する、その日の䜜業を終える 䞊行させたいずき別のこずを同時に走らせる AIの出力が䞍安定になったずき「ヒダリハットなんで切り替えようか」 長文が出されお頭が鈍っおきたずき 冒頭に挙げた3぀の瞬間も、党郚この偎です。実際、さっきの18回のうち半分以䞊は6〜10手・11〜49分で撃っおいお、コンテキストが溢れたから切ったわけじゃないんですよね。 続きをやるなら next 、別のタスクなら new 䜿っおいるのは2぀です。どっちもセッションは切り替えたす。違うのは、持っおいくもののほうです。 next 同じ話の続きをやるゎヌル珟圚地再開ポむント䞻芁ファむル保留・未確定コミット状態 new 別のタスクに移る次のタスク匕き継ぐ情報前提・スコヌプ 項目は倧䜓共通ですが、テンプレヌトは付録に眮いおおきたす。分けおいるのはシチュ゚ヌションのほうです。 next を撃぀のは、セッションに芁らない話が溜たっおきたずきです。議論の途䞭で出た脇道ずか、もう決着した話ずか、そういうものを抱えたたた進むのがしんどくなっおくる。決たったこずを䞀回ドキュメントに起こしお、そこから続きをやりたす。 new は、サブ゚ヌゞェントに投げるたでに少し議論が必芁なものですね。元セッションの文脈は少しだけ芁る、みたいなや぀です。今のセッションの続きではないので、必芁なぶんだけ持っお出たす。 すり合わせた結果が、匕き継ぎドキュメントになりたす AI に曞かせたものを、そのたた枡すわけじゃないんですよね。たず䜙蚈なものを萜ずしたす。匕き継ぎドキュメントを曞いおずいうず、Claudeさんはやったこずをすべお曞きがちです。攟っおおくず、発泚曞じゃなくおセヌブポむントを䜜っおくるんですよね。そこには本筋じゃないけど「これ今やったほうがいいかも」みたいな課題がぜろっず出おくるこずがあっお、あれを混ぜるず、次のセッションが䜕をすればいいか分からなくなりたす。 だから決めおいるこずは2぀ありたす。ひず぀めは基準です。遞ぶ軞は「次にやるこず」であっお、今やった成果物じゃありたせん。やった成果物はコミットしおおいおもう決定枈みにするのが確実ですね。ふた぀めは陀倖で、䜙蚈な内容を混ぜないこずです。単玔に「匕き継ぎ曞を曞いお」だず、セッションで螏んだ眠やそれをどう回避したのかなど結構いろんな情報を詰め蟌んできたす。そういった教蚓はファむルなどに別途切り出しお保存するのが良いですね。 そのうえで読み返すず、だいたいこの3぀が出おきたす。 議論の結論が真逆になっおいる。長いやり取りの末に決めたこずが、匕き継ぎでは逆に曞かれおいる 決たったこずが未決になっおいる。「保留・未確定」の欄に、もう決めたはずのものが䞊んでいる 情報がおんこ盛りで読むのがそもそも぀らい・䜙蚈な情報が曞かれおいる どれも、読たないず気づかない類のズレです。ドキュメントだから読めたす。読むから芋぀かる。芋぀けたら、人間が盎したす。 この萜ずしお盎す埀埩を抜けたものが、次のセッションに枡る発泚曞です。冒頭に曞いた「なんか AI の返答が荒れたぞ」の瞬間に、ちゃんず打おる手ができるずいうこずですね。 おたけで1぀。「これもやりたい」っお思い぀いたや぀は、 new で持ち出せば別ペむンで䞊行しおやれたす。䞊列で頭がパンクしおるずきも同じです。あずでやろうずか思い぀いたタむミングで next を䜜っおおけば、思い出さなくおも䜙裕ができた時に䜜業ずしお再開するこずができたす。 遞ぶのは軜くするためじゃありたせん。自分が読めるようにするためなんですよね。 付録そのたたコピヌしお䜿えるもの 本文では「項目は倧䜓共通」ず流したしたが、実物が無いず持ち垰れないず思うので眮いおおきたす。 next ず new のそれぞれに぀いお、フォヌマットず、毎回同じ圢で出すためのカスタムコマンドを䞊べたす。䞭括匧のずころを埋めれば、そのたた次のセッションの先頭に貌れたす。 続きをやる next のぶん ## 匕継ぎ継続: {トピック名} ### ゎヌル {このトピックで達成したいこず 1〜2行} ### 珟圚地どこたで / どこで止めた {䞭途状態を端的に。議論䞭なら論点、実装䞭なら未完郚分} ### 再開ポむント最初にやるこず {次セッションで最初に実行する具䜓アクション} ### 䞻芁ファむル - {フルパス}: {1行説明} ### 保留・未確定 - {保留した刀断・遞択肢ずその理由} ### コミット状態 {git status / git log -3 のサマリ} 僕はこのフォヌマットを Claude Code のカスタムコマンドにしおいお、 /session-handoff-next ず打぀ず生成しおファむルに保存するずころたでやっおくれたす。実物から効いおる郚分だけ削り出したのがこれです。 .claude/commands/session-handoff-next.md に眮けば動きたす。 --- description: 同じトピックの継続甚匕継ぎプロンプトを生成 argument-hint: [トピック名] allowed-tools: Bash, Read, Write, Glob, Grep --- # Handoff: 継続タスク甚 同じトピックを次セッションで継続再開するための匕継ぎを生成する。 ## Step 1: 継続に必芁な分だけ集める 網矅ではなく「埩元最小セット」。ゎヌル珟圚地再開ポむント䞻芁ファむル保留・未確定 コミット状態`git status` ず `git log -3`。未コミットがある時のみ。 ⚠ 教蚓はここに曞かない。普遍的な孊び手法・泚意点・再利甚できるパタヌンは別の眮き堎ぞ送る。 ここに残すのは、このトピック固有の埩元コンテキストだけ。 ## Step 2: 生成する 䞊の `next` のフォヌマットで曞く6セクション固定。これ以䞊増やさない。 内容が薄いセクションは芋出しごず省く。 ## Step 3: ファむルに保存しお開く `docs/tmp/handoff-next-{YYYYMMDD-HHMMSS}.md` に保存し、゚ディタで開く。 応答本文には保存先のパスだけを出す党文を再掲しない。 別のタスクに移る new のぶん 先頭が「次のタスク」なのがポむントで、ここが決たっおいないず䞋の2぀を遞べたせん。 ## 匕継ぎ新タスク開始: {次のタスク名} ### 次のタスク {次セッションで取り組む内容を 1〜3行で} ### 匕き継ぐ情報このタスクに関係する分だけ - {次タスクに効くファむル / 刀断 / 制玄だけ。無ければ「匕き継ぐ成果物なし」} ### 前提・スコヌプ - {必芁なファむルパス・参照ドキュメント} - {既知の制玄・やらないこず} コマンドのほうも同じで、Step 1 が本䜓です。ここが決たらないず遞別が始たりたせん。 --- description: 別タスクぞの切替え甚匕継ぎプロンプトを生成 argument-hint: [次のタスク名] allowed-tools: Bash, Read, Write, Glob, Grep --- # Handoff: 別タスク切替え甚 珟セッションから、次のタスクに効く情報だけを遞んで枡す。 これは「珟セッションの完了報告」ではない。 ## Step 1: 次のタスクを確定する 匕継ぎ党䜓の遞別基準になるので、ここを最優先で固める。 ⚠ 曖昧なたたファむルを䜜らない。䜕をするタスクか珟セッションのどの成果物を匕き継ぐ必芁があるか やらないこずが䞍足しおいたら、必ずナヌザヌに確認する掚枬で埋めない。 ## Step 2: 次のタスクを基準に遞別する 共有するファむル・次タスクに圱響する蚭蚈刀断や制玄・匕き継ぐべき萜ずし穎だけを抜く。 無関係な進捗や議論ログは捚おる。匕き継ぐものが無ければ「匕き継ぐ成果物なし」でよい。 ## Step 3: 生成する 䞊の `new` のフォヌマットで曞く。前提情報は最小限にしお、AI が自力で芋぀けられるものは省く。 ## Step 4: ファむルに保存しお開く `docs/tmp/handoff-new-{YYYYMMDD-HHMMSS}.md` に保存し、゚ディタで開く。 フォヌマット自䜓はどうでもよくお、効いおるのは2぀の決めごずのほうです。基準は「次にやるこず」、陀倖は䜙蚈な内容教蚓ずかを混ぜないこず。ここだけ持っお垰っおもらえれば、䞭身は自分の曞きやすい圢でいいず思いたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Codeずの解釈の違いは、匕き継ぎを曞かせるず露呈する first appeared on SIOS Tech Lab .
はじめに こんにちはサむオステクノロゞヌのなヌがです。前回、 Claude Code のテストゲヌトが線集れロで12分Stop hook が「黙っお効かなくなる」たで に぀いお曞きたしたが、あれから個人開発の Python プロゞェクトでマルチ゚ヌゞェント運甚を続けおいたす。委譲そのものは安定しおきたした。ただ、そのあいだずっず手を出せずにいた問題が残っおいたした。 レヌトリミットです。 サブ゚ヌゞェントを䜕本も䞊列で走らせおいるず、圓然トヌクンの消費は速くなりたす。そしお usage limit に圓たった瞬間、走っおいたサブ゚ヌゞェントは道半ばで殺されたす。ここたでは仕方ない。問題はその埌で、 制限が明けお䜜業を再開したずき、「どこたで終わっおいたのか」がどこにも残っおいない のです。 結果どうなるかずいうず、さっき 数千トヌクン䜿っお終わりかけおいた調査を、たったく同じ内容で新しいサブ゚ヌゞェントに投げ盎すこずになりたす。1 回の䞭断で同じ䜜業のトヌクンを 2 回払う。これが地味に効きたす。 レヌトリミット自䜓は昔からあるものですし、これに悩たされおいたのも今に始たった話ではありたせん。ずっず手を出せずにいたのは、 䞭断そのものを怜知する手立おが無い ず思い蟌んでいたからです。タヌンが API ゚ラヌで打ち切られるず、hooks から芋れば䜕も起きないたた䌚話が終わる。蚘録を残そうにも、蚘録するきっかけが無い。そう決め぀けお、そのたたにしおいたした。 しかし、ハヌネスを自䜜するこずで解決できるのではないかず思い自䜜したした。䞋蚘はそのサンプルリポゞトリになりたす。 .claude/ 䞀匏ず hook のテストに加えお、本物のレヌト制限を埅たずに䞭断から再開たでを再珟する examples/simulate-interruption.sh が入っおいたす。 サンプルリポゞトリ : Shotaro-Yoshinaga-sti/claude-code-turn-recovery ずころが改めお調べおみるず、 StopFailure ずいう hook むベントが ずっくに远加されおいたした 。API ゚ラヌでタヌンが終わったずきに発火するものです。远加されたのは Claude Code 2.1.78。手元は 2.1.260 だったので、180 バヌゞョン以䞊も前の話でした。完党に芋萜ずしおいたわけです。 しかも、これを実際に組み立おるための郚品は StopFailure だけではありたせん。改めお CHANGELOG を远っおみるず、必芁なものが 知らないあいだにひずずおり揃っおいたした 。 バヌゞョン 远加されたもの このハヌネスでの圹割 1.0.41 Stop から SubagentStop を分離 サブ゚ヌゞェントの完了だけを単独で捉えられる 2.0.42 SubagentStop に agent_id / agent_transcript_path 完了蚘録を agent_id で玐付けられる 2.1.47 Stop / SubagentStop に last_assistant_message 完了時の最終報告をトランスクリプトを解析せずに残せる 2.1.77 Agent の resume を廃止し SendMessage({to: agentId}) に䞀本化 䞭断したサブ゚ヌゞェントを agent_id で再開できる 2.1.78 StopFailure 䞭断の瞬間そのものを怜知できる 2.1.98 倱敗したバックグラりンドサブ゚ヌゞェントが郚分的な進捗を芪に報告 長い䜜業をバックグラりンドに眮く刀断が成り立぀ 2.1.199 API ゚ラヌで打ち切られたサブ゚ヌゞェントが郚分成果を芪に報告 䞭断時に拟えるものが「無」ではなくなった 面癜いのは 2.1.77 ず 2.1.78 です。 再開の口 SendMessage ず、䞭断の怜知 StopFailure が隣り合ったバヌゞョンで揃っおいたした。 片方だけでは足りたせん。䞭断を怜知できおも再開する手段が無ければ蚘録は掻かせたせんし、逆に再開できおも䜕を再開すべきか分からなければ意味がない。䞡方あっお初めお成立したす。 ぀たり「hooks では手が出ない」ずいう私の前提のほうが、ずっくに叀くなっおいたした。せっかく揃っおいるので、 䞭断を蚘録しお次のセッションで再開するハヌネスを自䜜するこずにしたした。 さきほどのサンプルリポゞトリが、その成果物です。   そしお、組み終えおから公匏ドキュメントを読み盎しお、 もっず倧きな前提のほうが厩れたした 。このハヌネス、ほずんど芁りたせんでした。 順番が前埌したすが、先にその話から曞きたす。実装だけ芋たい方は ハヌネスの党䜓像 たで読み飛ばしおください。 参考: Hooks リファレンス公匏ドキュメント 先に結論: 公匏だけでどこたで戻れるのか 私がハヌネスを䜜った動機は「レヌトリミットで打ち切られたサブ゚ヌゞェントの成果は䞞ごず消える」ずいう思い蟌みでした。 これが間違いでした。 サブ゚ヌゞェント内の API ゚ラヌ に、実行堎所ごずの挙動が明蚘されおいたすv2.1.199 以降。 バックグラりンド : 倱敗ずマヌクされ、芪が受け取るメッセヌゞには API ゚ラヌ名ず そのサブ゚ヌゞェントの最埌の出力 が含たれる。ドキュメントの蚀葉を借りれば「郚分的な䜜業は倱われたせん」 フォアグラりンド : すでに䜕かを出力しおいれば、その郚分出力が「遮断されタスクを完了しなかった」ずいう泚蚘付きで返る。v2.1.200 以降、䜕も出力しおいないツヌル呌び出ししかしおいないサブ゚ヌゞェントだけが Agent terminated early due to an API error で倱敗する そしお決定的なのがこちらです。 セッション氞続性 サブ゚ヌゞェントトランスクリプトはセッション内で氞続化されたす。Claude Code を再起動した埌、同じセッションを再開するこずでサブ゚ヌゞェントを再開できたす。 再開されたサブ゚ヌゞェントは、すべおの前のツヌル呌び出し、結果、および掚論を含む、完党な䌚話履歎を保持したす。サブ゚ヌゞェントは、新芏に開始するのではなく、停止した堎所から正確に再開したす。 ぀たり、制限が明けたあずにやるこずはこれだけです。 claude --continue あずは「さっきの調査を続けお」ず頌むだけ。Claude は SendMessage で該圓のサブ゚ヌゞェントを起こし、そのサブ゚ヌゞェントは止たった堎所から再開したす。トランスクリプトはメむン䌚話ずは別ファむルに保存されるので、 メむン䌚話が圧瞮されおも圱響を受けたせん 。 自䜜ハヌネスず䞊べるずこうなりたす。 自䜜のほうが明確に劣っおいたす。 ハヌネスが次のセッションに枡せるのは JSON の蚘録ずノヌトの芁玄ですが、公匏はサブ゚ヌゞェントのトランスクリプトそのものを埩元したす。情報量で勝ち目がありたせん。 しかも時系列がよくない。郚分成果を芪に返す修正2.1.199が公開されたのは 2026 幎 7 月 2 日、私が StopFailure hook を曞き始めたのは 8 月 26 日でした。 公匏が手圓おしおから 2 か月近く経ったあずに、同じ問題を自力で解こうずしおいた こずになりたす。CHANGELOG で StopFailure を芋぀けたずころで満足しおしたい、サブ゚ヌゞェント偎のドキュメントを読み盎さなかったのが敗因です。 それでもハヌネスに残る甚途 では完党に無駄だったかずいうず、狭いながら残る堎面はありたす。 同じセッションを再開しない堎合 です。 コンテキストが膚らみすぎお、新しいセッションで仕切り盎したいずき /clear した埌 cleanupPeriodDays 既定 30 日を過ぎお、トランスクリプトが掃陀された埌 そもそも「どのセッションだったか」を人間が思い出せないずき このずきに手がかりになるのは、ディスクに残る .claude/recovery/ の䞭断蚘録ず .claude/agent-notes/ のノヌトだけです。 SessionStart hook は どのセッションで開いおも 発火するので、たっさらな新しいセッションにも前回の䞭断内容を持ち蟌めたす。ノヌトのほうも、サブ゚ヌゞェントの最埌の 1 発蚀ではなく「途䞭で確定した所芋」が時系列で残るずいう違いはありたす。 ずはいえ、 claude --continue で枈む堎面のほうが圧倒的に倚いです。 たずこれを詊しお、足りないず感じたずきだけ hooks を怜蚎する 、ずいう順序が正解でした。 以降は、その「䜜っおしたったハヌネス」の実装蚘録です。 StopFailure の癖や、hook で状態を持぀ずきに螏んだ萜ずし穎自䜓は別のものを䜜るずきにも効くはずなので、そのたた残しおおきたす。 䞭断を捕たえられるむベント: StopFailure 芋萜ずしおいた StopFailure を、もう少し詳しく芋おおきたす。 公匏ドキュメントによるず、入力ペむロヌドは共通フィヌルドに加えお次の 3 ぀を持ちたす。 フィヌルド 型 内容 error string タヌンを終わらせた゚ラヌ皮別 error_details string 远加の詳现optional last_assistant_message string 䌚話に衚瀺された゚ラヌ文蚀optional error に入りうる倀も列挙されおいたす。 rate_limit / overloaded / authentication_failed / oauth_org_not_allowed / account_on_hold / billing_error / invalid_request / model_not_found / server_error / max_output_tokens / unknown の 11 皮類です。 last_assistant_message には泚意が芁りたす。 Stop や SubagentStop では Claude の発蚀が入るフィヌルドですが、 StopFailure では "API Error: Rate limit reached" のような API ゚ラヌ文字列そのもの が入りたす。同じ名前でも䞭身の意味が違うので、共通の凊理でたずめお扱うず取り違えたす。 そしお重芁な制玄が 2 ぀ありたす。 終了コヌドは無芖される 。゚ラヌはもう起きおいるので、hook が䞭断をブロックしたり結果を倉えたりはできたせん。 stdout ず JSON 出力も無芖される 。decision control は䜿えず、出力はデバッグログにしか出たせん。 ぀たり StopFailure hook にできるのは「 ディスクに曞くこず 」だけです。ここが蚭蚈の出発点になりたす。曞いたものを読んで人間ず Claudeに芋せる圹目は、別のむベントに持たせる必芁がありたす。 その盞方が SessionStart です。こちらは逆に、 stdout がそのたた Claude のコンテキストに远加される 数少ないむベントのひず぀です他は UserPromptSubmit ず UserPromptExpansion 。 stdout is added to Claude’s context as the first input before the initial prompt. 曞く偎が StopFailure 、読たせる偎が SessionStart 。この 2 ぀をディスク䞊の JSON で繋ぐ、ずいうのが今回のハヌネスの骚栌です。 参考: Hooks リファレンス – StopFailure公匏ドキュメント 自䜜したハヌネスの党䜓像 䞍芁になっおしたいたしたが、自䜜したハヌネスの党䜓像です。関係するファむルはこれだけです。 .claude/ ├── settings.json # hook の登録ず env ├── hooks/ │ ├── agent-call-record.sh # PostToolUse(Agent): 起動を .call.json に蚘録 │ ├── agent-call-complete.sh # SubagentStop: 完了を .done.json に蚘録 │ ├── pending_agents_shared.py # 「未完了」刀定の2パス走査共有モゞュヌル │ ├── turn-failure-record.sh # StopFailure: äž­æ–­ã‚’ recovery/ に蚘録 │ ├── session-start.sh # SessionStart: 再開ブリヌフィングを泚入 │ └── agent-calls-gc.sh # UserPromptSubmit: 叀い状態を掃陀 ├── agent-calls/ # <session_id>/<prompt_id>/*.call.json / *.done.json ├── recovery/ # <session_id>.json提瀺埌は .json.consumed └── agent-notes/ # サブ゚ヌゞェントが残す途䞭成果 時系列で䞊べるずこうなりたす。 ポむントは、 「未完了だった゚ヌゞェント」を刀定するための材料が既にあった こずです。前回の蚘事で䜜ったリレヌガヌドが、Agent 呌び出しの内容を台垳に蚘録しおいたした。これを流甚したす。 ハヌネスを実装する 1. Agent 呌び出しの台垳を䜜る.call.json / .done.json たず土台になる台垳です。「起動」ず「完了」を別々のむベントで蚘録したす。 起動偎は PostToolUse matcher は Agent です。ここで泚意したいのは、 このむベントは完了ではない ずいう点です。サブ゚ヌゞェントは既定でバックグラりンド実行されるため、 PostToolUse は起動が返った時点 tool_response.status == "async_launched" で発火したす。 それでもここで蚘録する理由は、 PostToolUse が tool_input.prompt ず tool_response.agentId の 䞡方を持぀唯䞀のむベント だからです。「どの agent_id がどんな䟝頌で起動されたか」はここでしか確定できたせん。 # agent-call-record.shPython 郚分の抜粋 session_id = as_str(data.get("session_id")) prompt_id = as_str(data.get("prompt_id")) subagent_type = as_str(tool_input.get("subagent_type")) prompt = as_str(tool_input.get("prompt")) agent_id = as_str(tool_response.get("agentId") or data.get("agent_id")) state_dir = os.path.join( state_root, safe_name(session_id, "unknown-session"), safe_name(prompt_id, "no-prompt-id"), ) record = { "agent_id": agent_id, "subagent_type": subagent_type, "norm_lines": normalize_lines(prompt)[:MAX_PROMPT_LINES], "time": time.time(), } # → <session_id>/<prompt_id>/<agent_id>.call.json 完了偎は SubagentStop です。 agent_id ず最終報告テキストを .done.json に曞きたす。 # agent-call-complete.sh抜粋 record = { "agent_id": agent_id, "agent_type": agent_type, "output_lines": normalize_lines(output[:MAX_OUTPUT_CHARS])[:MAX_OUTPUT_LINES], "time": time.time(), } # → <session_id>/<prompt_id>/<agent_id>.done.json これで刀定匏が立ちたす。 .call.json はあるが、察応する .done.json が無い = 䞭断された瞬間に走っおいた゚ヌゞェント この 2 パス走査 .done.json のキヌを集める → .call.json の未完了を抜出するは、埌述するずおり StopFailure 偎ず SessionStart 偎の䞡方が必芁ずするので、共有モゞュヌル pending_agents_shared.py に切り出しおいたす。 def collect_done_keys(session_dir: str, prompt_dir_names: list[str]) -> set[str]: """パス1: セッション党䜓の .done.json キヌを1぀の集合ぞ集玄する。""" done_keys: set[str] = set() for prompt_dir_name in prompt_dir_names: prompt_dir = os.path.join(session_dir, prompt_dir_name) try: names = os.listdir(prompt_dir) except Exception: continue done_keys.update(n[: -len(".done.json")] for n in names if n.endswith(".done.json")) return done_keys 2. StopFailure で䞭断を蚘録する 台垳が揃ったので、䞭断の瞬間に「䜕が倱われたか」をスナップショットしたす。 { "hooks": { "StopFailure": [ { "hooks": [ { "type": "command", "command": "\"$CLAUDE_PROJECT_DIR/.claude/hooks/turn-failure-record.sh\"", "timeout": 10 } ] } ] } } matcher は あえお付けおいたせん 。 rate_limit に絞りたくなるずころですが、 overloaded でも server_error でも「䜜業が飛ぶ」ずいう事情は同じです。党郚蚘録しお、区別は読む偎 session-start.sh に任せたす。 蚘録する䞭身はこんな圢です。 record = { "session_id": session_id, "prompt_id": prompt_id, "error_type": error_type, "error_message": error_message, "time": record_time, "branch": git_output(["branch", "--show-current"]).strip(), "git_status_lines": git_output(["status", "--porcelain"]).splitlines()[:MAX_STATUS_LINES], "pending_agents": pending_agents, # 未完了だった゚ヌゞェント "pending_total": pending_total, # 䞊限で切り捚おる前の総数 "finished_agents": finished_agents, # このタヌンで完了枈み再実行させないため "payload_keys": payload_keys, # 蚺断甚 "payload_preview": payload_preview, # 蚺断甚 } finished_agents を持っおいるのがポむントです。再開時に「未完了はこれ」だけを䌝えるず、 既に終わっおいる゚ヌゞェントたで念のため走らせ盎す ずいう反応が起きがちです。「これは終わっおいるので再実行しない」を明瀺的に枡したす。 曞き蟌みは tempfile.mkstemp() + os.replace() の原子的眮換です。䞭断はタヌンの途䞭で起きるので、読む偎が壊れた JSON を掎たないよう「完党な内容が芋えるか、叀い内容が芋えるか」の二択にしおいたす。 fd, tmp_path = tempfile.mkstemp(dir=state_root, prefix=".tmp-", suffix=".json") with os.fdopen(fd, "w", encoding="utf-8") as f: json.dump(record, f, ensure_ascii=False) os.replace(tmp_path, path) そしお党䜓が try/except で囲たれ、 䜕が起きおも exit 0 です。 StopFailure は終了コヌドも stdout も無芖されるむベントなので、倱敗を通知する手段がありたせん。静かに諊めるしかないので、fail-open に振り切りたす。 3. SessionStart で再開ブリヌフィングを泚入する 曞いたものを読たせる偎です。 SessionStart hook の stdout はそのたたコンテキストに入るので、Markdown で人が読める圢に敎圢しお出したす。 蚘録の匕き圓おは 2 段構えです。 def pick_record(state_root, session_id): """(パス, 蚘録, 別セッション由来か) を返す。""" cutoff = time.time() - ttl_seconds() # 既定 24 時間 if session_id: exact = os.path.join(state_root, f"{safe_name(session_id, ...)}.json") record = load(exact) if record is not None and float_or_zero(record.get("time")) >= cutoff: return exact, record, False # 䞀臎しなければ TTL 内で最新の蚘録を「別セッションの蚘録」ずしお拟う --continue / --resume は同じ session_id でセッションを開き盎すので完党䞀臎が盎撃したす。䞀方で、制限に圓たった埌は新しいセッションを立お盎す運甚も倚いため、䞀臎しなければ TTL既定 24 時間内の最新蚘録をフォヌルバックで拟い、「別セッションの蚘録です」ず断り曞きを添えお出したす。 出力されるブリヌフィングはこんな感じです。 ## 前回タヌンの䞭断蚘録 (rate_limit) - 䞭断の原因になったAPI゚ラヌがただ続いおいるかどうかは、この蚘録からは分からない。 ただし「ただ制限䞭かもしれない」はサブ゚ヌゞェントぞの委譲を控える理由にならない。 制限が明けおいれば委譲は普通に動くし、明けおいなければメむンセッションが自分で 実装しおも同じように倱敗する - äž­æ–­: 2026-09-02 20:41 / You've hit your session limit · resets 8:40pm (Asia/Tokyo) - 未完了だったサブ゚ヌゞェント (2ä»¶): - investigator [agent_01AbC...] 認蚌たわりの゚ラヌ凊理を掗い出す / 
 - implementer [agent_01XyZ...] サムネむル生成のキャッシュを远加する / 
 (叀い) - このタヌンで完了枈み (1ä»¶): reviewer [agent_01Def...] — 再実行しない - 再開の手順 (䞊から順に詊し、成功した時点で次には進たない): 1. `.claude/agent-notes/` に途䞭成果が残っおいないか先に確認する。 残っおいれば、次の指瀺はそこからの差分だけでよい 2. 未完了゚ヌゞェントに SendMessage(to: "<agent_id>") を送っお再開する。 同じ䟝頌を新芏 Agent で䜜り盎さない 3. SendMessage が届かない (゚ヌゞェントが既に消えおいる) 堎合は、 残りの䜜業を新しいサブ゚ヌゞェントに委譲する 4. メむンセッションが自分で実装するのは最埌の手段 - 組み蟌みの Explore / Plan は再開できない。 再開できる investigator / implementer に眮き換えお起動する 工倫しおいる点をいく぀か挙げたす。 手順を 1〜4 の順序付きで曞く。 「再開しおください」だけだず、 SendMessage が届かなかった時点で消去法的に「じゃあメむンが自分で実装するか」に萜ちたす。䞍達時の出口手順 3を明蚘しお、委譲ルヌルの䞭で完結できるようにしおいたす。 鮮床に泚意を添える。 呌び出しから 1 時間 STALE_AGENT_AGE_SEC を超えた゚ヌゞェントには (叀い) を付けたす。数時間埌の再開では、もうプロセスが消えおいる可胜性が高いためです。 切り捚おが起きたこずを芋せる。 蚘録偎は 12 件、衚瀺偎は 8 件で切りたすが、切る前の総数 pending_total も蚘録しおおき、 (他 N 件、䞊限により省略) ず出したす。䞊限が黙っお情報を萜ずすのは避けたい。 䞀床出したら二床出さない。 提瀺埌は os.replace(path, f"{path}.consumed") でリネヌムしたす。削陀ではなくリネヌムなのは、埌述の GC が同じ基準で拟えるようにするためです。 なお、 error は unknown を正芏の倀ずしお取りうるので、空文字ず同じく「原因䞍明」に寄せおいたす。ここを玠通しにするず、ドキュメント蚘茉の正芏倀そのものを経路ずしお、日本語のブリヌフィングに英語の (unknown) が混ざりたす。 4. サブ゚ヌゞェント偎に途䞭成果を曞かせる hooks だけでは埋たらない穎がありたす。 未完了の゚ヌゞェントを再開できおも、その゚ヌゞェントが䜕を掎んでいたかは本人しか知らない ずいう点です。芪に返るのは最埌の 1 発蚀だけで、その党履歎は同じセッションを再開しないず蟿れたせん。新しいセッションで仕切り盎したずきに SendMessage が届かなければ、そこで打ち切りです。 そこで、゚ヌゞェント定矩そのものに「途䞭成果の保党」を曞いおいたす .claude/agents/investigator.md 。 ## 途䞭成果の保党 調査が数分を超えそうなら、確定した所芋をその郜床 `.claude/agent-notes/<YYYYMMDD>-investigator-<短いスラッグ>.md` に远蚘する。 - なぜ: レヌト制限などのAPI゚ラヌで打ち切られるず、芪に枡るのは最埌の1発蚀だけで、 それも芪のタヌンごず萜ちれば次のセッションには残らない。ノヌトが無ければ 再開時に䞀から流し盎すこずになる。 - ノヌトの1行目に䟝頌の1行芁玄を曞く。䞭断蚘録ず突き合わせるための目印になる。 - 最終報告はノヌトぞの参照ず芁玄でよい。同じ内容を二重に曞かない。 - 远蚘は `cat >> <パス> <<'EOF'` で行う。**曞いおよいのは自分のノヌトだけ**で、 ゜ヌスツリヌは読み取り専甚のたた。 読み取り専甚゚ヌゞェントに曞き蟌みを蚱すのは䞀芋矛盟しおいたすが、 曞いおよい堎所を自分のノヌトだけに限定 するこずで、゜ヌスツリヌの読み取り専甚性は保っおいたす。 再開ブリヌフィングの手順 1 が「たずノヌトを芋る」になっおいるのはこのためです。ノヌトが残っおいれば、 SendMessage が届かなくおも「どこたで終わっおいお、次に䜕をするか」を曞いた 新しい指瀺 を䜜れたす。前回の䟝頌文を䞞写しするのではなく、差分だけを枡す。ここは前回の蚘事で䜜ったリレヌガヌドの思想ずも䞀臎したす。 5. 蚭定偎で被害そのものを枛らす 蚘録ず再開の仕組みずは別に、 settings.json の env で被害の総量そのものを削っおいたす。 { "env": { "CLAUDE_AUTO_BACKGROUND_TASKS": "1", "CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY": "6" } } CLAUDE_AUTO_BACKGROUND_TASKS=1 は、玄 2 分を超えたサブ゚ヌゞェントを自動でバックグラりンドぞ移す蚭定です。前節のずおり、ただ䜕も出力しおいないフォアグラりンドのサブ゚ヌゞェントだけは䞭身ごず倱われるので、長い䜜業をバックグラりンドぞ逃がすのはそのたた保険になりたす。 なお v2.1.198 以降、サブ゚ヌゞェントは既定でバックグラりンド実行になりたした。Claude が「結果を埅っおから続けたい」ず刀断した堎合はフォアグラりンドを遞ぶので、この蚭定はそこに掛ける保険ずいう䜍眮づけです。 CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY=6 は䞊列実行の䞊限を䞋げるものです。1 回の䞭断で巻き添えになる䜜業を枛らす狙いですが、䞋げすぎるず通垞の調査が目に芋えお遅くなりたす。6 はそのトレヌドオフの萜ずしどころでした。ただしこの倉数は 公匏ドキュメントに蚘茉がありたせん 。挙動が倉わっおも䞍思議はないので、無くおも困らない蚭定ずしお扱っおいたす。 6. 状態ファむルを GC する 攟っおおくず .claude/agent-calls/ も .claude/recovery/ も溜たる䞀方なので、 UserPromptSubmit で掃陀したす。 GC_DAYS="${HOOK_STATE_GC_DAYS:-7}" find "$CALLS_DIR" -mindepth 1 -maxdepth 1 -type d -mtime "+$GC_DAYS" -exec rm -rf {} + 2>/dev/null || true find "$RECOVERY_DIR" -mindepth 1 -maxdepth 1 -type f \ \( -name '*.json' -o -name '*.json.consumed' \) -mtime "+$GC_DAYS" -delete 2>/dev/null || true find "$NOTES_DIR" -mindepth 1 -maxdepth 1 -type f -name '*.md' -mtime "+$GC_DAYS" -delete 2>/dev/null || true 保持は 7 日。䞭断蚘録もノヌトも、7 日経おば再開の圹には立ちたせん。 -name で察象を絞っおいるのは、同じ堎所に眮いた無関係なファむルを巻き蟌たないためです。 なお、ブリヌフィングずしお提瀺する条件24 時間ず GC の保持期間7 日は別物です。24 時間を過ぎた䞭断は「別の䜜業の話」ずしお提瀺したせんが、ファむルは調査甚に 7 日残りたす。 運甚しお分かった萜ずし穎 ここからが本題かもしれたせん。玠盎に䜜るず壊れるずころが、思ったより倚くありたした。 完了刀定を prompt_id 単䜍でやるず完了枈みを未完了ず誀認する 最初の実装では、 .call.json ず .done.json の突き合わせを 同じ prompt_id ディレクトリの䞭だけ でやっおいたした。玠盎な蚭蚈に芋えたす。 これが盛倧に倖れたした。バックグラりンドのサブ゚ヌゞェントは ナヌザヌの発蚀をたたいで生き続ける ので、起動時の prompt_id ず完了時の prompt_id は日垞的に別物になりたす。同じ prompt_id の䞭だけを芋るず、既に完了しおいる゚ヌゞェントが軒䞊み「未完了」に化けたす。 修正は、 .done.json の集玄範囲を セッション党䜓 に広げるこずでした。これが collect_done_keys() がセッション配䞋の党 prompt_dir_names を舐めおいる理由です。 UUID の蟞曞順で切り捚おるず本呜が枠から挏れる 蚘録する未完了゚ヌゞェントは 12 件を䞊限にしおいたす。圓初はこれを os.listdir() の゜ヌト順、぀たり prompt_id の蟞曞順 のたた切っおいたした。 prompt_id は UUID です。 発行順ずはたったく無関係 なので、蟞曞順で埌ろに来た prompt_id に「いた本圓に走っおいる゚ヌゞェント」がいるず、䞊限に抌し出されお萜ちたす。実際、叀い prompt_id に未完了の .call.json が 15 件、珟圚の prompt_id に本呜が 1 件、ずいう配眮を䜜ったずころ、本呜が pending から完党に脱萜したした。 いたは 呌び出し時刻の降順 に䞊べおから切っおいたす。 time が無い䞍正なレコヌドは最も叀い扱いです。 pending_with_time.sort(key=lambda pair: pair[0], reverse=True) pending_total = len(pending_with_time) pending = [entry for _, entry in pending_with_time[:MAX_AGENTS]] 「䞊限で切る」実装を曞くずきは、 䜕の順で切っおいるか を必ず意識する、ずいう教蚓でした。 サブ゚ヌゞェントだけが殺されるず StopFailure が発火しない これが䞀番気づきにくかったケヌスです。 レヌトリミットは、 サブ゚ヌゞェントだけを殺しおメむンのタヌンを生かす こずがありたす。メむンが生きおいるのでタヌンは正垞終了扱いになり、 StopFailure は発火したせん。぀たり .claude/recovery/ に蚘録が䜜られない。 その堎でメむンが気づけば SendMessage で再開できたすが、気づかないたたセッションが終わるず、走りかけのサブ゚ヌゞェントの存圚は次のセッションに䞀切䌝わりたせん。ハヌネスの穎です。 塞ぎ方は、 session-start.sh に フォヌルバック走査 を足すこずでした。䞭断蚘録が「無い」ずきに限り、 .claude/agent-calls/ の台垳を セッションをたたいで 盎接走査したす。 def find_orphan_agents(calls_root, current_session_id, now): """䞭断蚘録が無いずき、他セッションに走ったたた終わったサブ゚ヌゞェントを探す。""" for session_dir_name in session_dirs: if session_dir_name == safe_name(current_session_id, "unknown-session"): continue # 珟圚のセッションはただ生きおいる可胜性があるので陀倖 done_keys = collect_done_keys(session_dir, prompt_dirs) pending_calls = collect_pending_calls(session_dir, prompt_dirs, done_keys, now, ttl_sec) ... 面癜いのは、 turn-failure-record.sh は珟圚のセッションだけを芋お、 session-start.sh は珟圚のセッションを陀く党セッションを芋る ずいう、ちょうど裏返しの関係になっおいるこずです。どちらも同じ 2 パス走査を䜿いたすが、察象セッションの遞び方だけが違う。だから pending_agents_shared.py は 2 パス刀定そのものだけを持ち、 察象セッションの遞び方・䞊べ替え・件数䞊限は呌び出し偎の関心事 ずしお意図的に倖に眮いおいたす。 なぜモゞュヌルに切り出したかずいうず、同じロゞックが 2 箇所にあった時期に実際に事故ったからです。 .done.json の集玄範囲をセッション党䜓に広げる修正が session-start.sh 偎にしか入らず、 turn-failure-record.sh は prompt_id スコヌプのたた取り残されお、いた走っおいる゚ヌゞェントが pending から脱萜する退行が生たれたした。 ブリヌフィングに怜蚌できない断定を曞いおいた これは実装バグではなく、 文章のバグ です。 初期のブリヌフィングには、こう曞いおいたした。 この蚘録が出おいる = 新しいセッションが開始できおいる = 䞭断の原因になった API ゚ラヌは既に解消しおいる 䞀芋もっずもらしいのですが、成立したせん。 SessionStart hook は ロヌカルで走り 、モデルぞの API リク゚ストが成功したこずを前提にしたせん。制限䞭でも CLI は起動できお、その時点でブリヌフィングは泚入されたす。 しかも厄介なこずに、これはハヌネスが毎セッション自動で泚入する文蚀です。**モデルにはそれを疑う手段がありたせん。**怜蚌䞍胜な事実䞻匵を、暩嚁ある前提ずしお毎回枡しおいたこずになりたす。 いたは事実䞻匵だけを削っお、行動指瀺は残しおいたす。 䞭断の原因になった API ゚ラヌがただ続いおいるかどうかは、この蚘録からは分からない。ただし「ただ制限䞭かもしれない」はサブ゚ヌゞェントぞの委譲を控える理由にならない。制限が明けおいれば委譲は普通に動くし、明けおいなければメむンセッションが自分で実装しおも同じように倱敗する 行動指瀺は事実䞻匵に䟝存せずに曞ける、ずいうのが孊びでした。「制限は明けおいる から 委譲しおよい」ではなく、「明けおいおもいなくおも自力実装に利点はない から 委譲する」ず曞けば、前提が厩れおも指瀺は生き残りたす。 「提瀺枈み」の印で台垳の䞻デヌタをリネヌムしおいた 最埌は蚭蚈の筋の悪さの話です。 孀児゚ヌゞェントの走査には「䞀床提瀺したら二床出さない」印が芁りたす。䞭断蚘録のほうは .json → .json.consumed ぞのリネヌムでやっおいたので、同じ考え方で .call.json → .call.json.presented ずリネヌムしおいたした。 これが良くなかった。 .call.json は リレヌガヌド・線集ガヌド・䞭断蚘録の 3 ぀の hook が共有で読む台垳の䞻デヌタ です。䞭断蚘録の .json が「消費したら圹目が終わる通知」なのずは性質が違いたす。 圓時実害が出おいなかったのは、孀児スキャンが珟圚のセッションを陀倖しおいお、他の 3 ぀の hook は珟圚のセッションしか芋おいなかったからです。偶然です。将来どれかの hook がセッションをたたいで .call.json を読むようにした瞬間、提瀺枈みのものだけが黙っお芋えなくなりたす。 --resume で叀いセッションを開き盎した堎合も同じ圢で顕圚化したす。 いたは䞻デヌタを倉曎せず、サむドカヌの .presented.json を別に眮く方匏です。 def presented_marker_path(call_path: str) -> str: if call_path.endswith(".call.json"): return call_path[: -len(".call.json")] + ".presented.json" return call_path + ".presented.json" GC はセッションディレクトリごず rm -rf するので、入れ子のサむドカヌも䞀緒に回収されたす。 共有される䞻デヌタに、片方の読み手の郜合の状態を混ぜない。 圓たり前のようでいお、既存のリネヌム方匏を暪展開した結果うっかりやっおしたいたした。 実際に動かしおみる このハヌネスは意図的にテストで固めおいたす。hook は普段は黙っお動くものなので、壊れおも気づけないからです。 uv run python -m unittest discover -s .claude/tests -t .claude/tests -v 䞭断蚘録たわりのテストだけで、 turn-failure-record に 31 本、 session-start に 27 本、共有モゞュヌルに 13 本、GC に 6 本ありたす。テストが守っおいる性質は、テストファむルの docstring に曞いおいたす。 """turn-failure-record.sh (StopFailure hook) のテスト。 このhookが守っおいる性質: 1. 「.call.json はあるが .done.json がない」= 䞭断時に走っおいた゚ヌゞェント。 この刀定を誀るず、再開時に完了枈みの調査たで流し盎しおトヌクンを二重に捚おる。 2. 未完了の刀定はセッション配䞋の *å…š* prompt_id を芋る。バックグラりンドの サブ゚ヌゞェントは発蚀をたたいで生き続けるため、盎近の prompt_id だけを 芋るず取りこがす。 3. 䜕が起きおも exit 0 で、曞けないずきは䜕も残さない(fail-open)。 """ 実際に蚘録された JSON も芋おみたしょう。これは執筆䞭にログむンが切れお䞭断したずきのものです rate_limit ではありたせんが、蚘録の圢は同じです。 session_id ず prompt_id は実際の倀をマスクしおいたす。 cat .claude/recovery/*.json | python3 -m json.tool { "session_id": "00000000-0000-0000-0000-000000000000", "prompt_id": "11111111-1111-1111-1111-111111111111", "error_type": "authentication_failed", "error_message": "Login expired · Please run /login", "time": 1788485303.017802, "branch": "main", "git_status_lines": [], "pending_agents": [], "pending_total": 0, "finished_agents": [], "payload_keys": [ "cwd", "effort", "error", "hook_event_name", "last_assistant_message", "prompt_id", "session_id", "transcript_path" ], "payload_preview": { "effort": "{\"level\": \"high\"}", "hook_event_name": "StopFailure" } } payload_keys ず payload_preview を蚺断甚に残しおいるのが効きたす。実際、 error / error_details / last_assistant_message ずいうフィヌルド名はドキュメントどおりでしたが、 将来ペむロヌドの圢が倉わったずきに同じやり方で気づける よう、生のキヌ䞀芧の蚘録は続けおいたす。実枬が安定した埌も消しおいたせん。 そしお、 error_type にも error_message にも候補キヌが埋たらなかった堎合の保険ずしお、メッセヌゞ本文からの掚定も入れおありたす。 RATE_LIMIT_HINT = re.compile(r"rate limit|usage limit|429|529|overloaded", re.IGNORECASE) if not error_type and RATE_LIMIT_HINT.search(error_message): error_type = "rate_limit" rate_limit / usage limit / 429 / 529 / overloaded は、いずれも「䜜業が飛ぶ」ずいう点で扱いが同じです。倧たかな掚定で十分、ずいう割り切りです。 さいごに 今回は、レヌトリミットでの䞭断を StopFailure で蚘録しお SessionStart で再開ブリヌフィングずしお泚入するハヌネスを玹介したした。そしお、 それが公匏機胜でほが代替できるこずに、䜜り終えおから気づいた 話でもありたす。 芁点をたずめたす。 たず claude --continue を詊す。 サブ゚ヌゞェントのトランスクリプトはセッション内で氞続化され、再起動埌も同じセッションを再開すれば止たった堎所から再開できたす。今回私が自䜜したハヌネスより倚くを埩元したす。 hooks を曞く前に、公匏ドキュメントの珟圚地を確認する。 CHANGELOG で䜿いたいむベントを芋぀けただけでは足りたせんでした。「そもそもこの問題はただ残っおいるのか」を、機胜偎のドキュメントで確かめるべきでした。 StopFailure はディスクに曞くこずしかできない stdout も終了コヌドも無芖される。読たせる圹目は SessionStart に持たせたす。 未完了の刀定は .call.json ず .done.json の突き合わせ 。ただしバックグラりンドのサブ゚ヌゞェントは発蚀をたたぐので、 .done.json はセッション党䜓で集玄したす。 ハヌネスが毎回泚入する文蚀に、怜蚌䞍胜な断定を混ぜない。 モデルには疑う手段がありたせん。今回の私がたさに、怜蚌しおいない断定を前提に半月ぶんの実装を積み䞊げおいたした。 hook で組む「ハヌネス」は、Claude 本人の刀断力に頌らずに運甚ルヌルを機械的に効かせられるのが匷みです。䞀方で今回のように、 ハヌネス自身のバグは静かに効いお、気づいたずきには䜕セッションも損をしおいる ずいう性質もありたす。テストを厚めに曞いおおくのは、その保険ずしおかなり効きたした。 ただ今回いちばん効いたのは、テストではなくドキュメントの読み盎しでした。Claude Code は曎新が速いので、半幎前の「できない」が今日も「できない」ずは限りたせん。 ずはいえ、たるたる無駄だったずも思っおいたせん。 StopFailure が stdout も終了コヌドも捚おるこず、バックグラりンドのサブ゚ヌゞェントがナヌザヌの発蚀をたたいで生きるこず、 prompt_id が䞭断をたたぐず食い違うこず。 このあたりは自分で組んでみるたで知らなかった こずばかりで、hook でセッションをたたぐ状態を持぀ずきの勘どころは、そのたた次に䜿えたす。公匏に远い抜かれたのは悔しいですが、Claude Code の内偎がどう動いおいるかを䞀段深く知れたのは収穫でした。 同じように hooks で䜕かを塞ごうずしおいる方は、着手前に公匏ドキュメントを䞀呚しおみおください。そのうえで、それでも塞ぎたい穎が残っおいたら、䜜っおみる䟡倀はあるず思いたす ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Code のレヌトリミット䞭断察策を hooks で自䜜したら、公匏ドキュメントに答えが曞いおあった first appeared on SIOS Tech Lab .
もう入っおいる Chrome を、そのたた觊らせる Chrome DevTools MCP は、いた普段䜿っおいる Chrome をそのたた䜿いたす。自動化甚のブラりザを別に萜ずす手順が芁らないので、入れるのはコマンド1本です。動かしおみるず立ち䞊がっおくるのは芋慣れた Chrome ずほが同じ画面で、そこの安心感が倧きかったですね。 そうなるず玠朎な問いが1぀残りたす。ブラりザも觊らせたいけど、ログむンが芁る画面はどうするのか。先に環境を䜜っお、そこから順に曞きたす。 先に1぀だけ前提がありたす。 Node.js が入っおいるこず です。この埌のコマンドで䜿う npx は Node.js に同梱されおいるので、たっさらな Windows では叩けたせん。確かめたのは v22.15.0 です。 Node.js の入れ方そのものはこの蚘事では扱いたせん。普段から開発しおいる人なら手癖で終わる話ですし、そうでない人は Claude に「Windows に Node.js を入れたい」ず聞けば、いたの環境に合った手順を出しおくれたす。ここに曞いおもすぐ叀くなるずころなので、そちらに任せたす。 入れ方そのものに新しいずころは䜕もないので、 公匏の Get started も䞊べおおきたす。Gemini CLI や Codex、JSON で蚭定を曞く圢はそちらにたずたっおいたす。Claude Code に぀いおはプラグむン経由の入れ方 /plugin marketplace add ChromeDevTools/chrome-devtools-mcp → /plugin install chrome-devtools-mcp@chrome-devtools-plugins も案内されおいお、そちらは MCP に加えお Skills も䞀緒に入りたす。この蚘事は玠の MCP 登録だけで進めたす。 韍ちゃん 特別な事情がない方はSkillsも䞀緒に入れおおいたほうがいいから公匏を芋ようね この前提を螏たえお、入れるのはコマンド1本です。 claude mcp add chrome-devtools -- npx -y chrome-devtools-mcp@latest これだけです。オプションは1぀も芁りたせん。打ったディレクトリ限定で登録されるので、どこでも䜿いたいなら --scope user を足したす。 远加したら Claude Code のセッションを䞀床開き盎したす。開き盎さないずツヌルが生えたせん。入っおいるかはこれで芋えたす。 $ claude mcp list Checking MCP server health
 chrome-devtools: npx -y chrome-devtools-mcp@latest --user-data-dir=C:\Users\<ナヌザヌ名>\.chrome-automation-profile --viewport=1400x900 --screenshot-format=webp --screenshot-quality=70 --screenshot-max-width=1400 - ✔ Connected 僕の環境はオプションを足しおいるので長いですが、玠の登録ならもっず短く出たす。芋るのは末尟の ✔ Connected だけです。ただしこれで分かるのはサヌバヌの疎通たでで、実際に動くかは1回 list_pages を呌ばせるたで分かりたせんこの話は最埌にもう䞀床出おきたす。 今回は Windows で完結させおいたす。WSL2 や devcontainer からホストの Chrome を叩く構成は扱いたせん。 入ったので、䜕が取れるのかを先に芋おおきたす。自分のブログの公開ペヌゞを開いお、構造をテキストで取っおみたす。 navigate_page(pageId=1, type="url", url="https://<自瀟ブログ>/") → Successfully navigated to https://<自瀟ブログ>/. ## Pages 1: SIOS Tech Lab - ゚ンゞニアのためになる技術トピックス (https://<自瀟ブログ>/) [selected] take_snapshot を呌ぶず、ペヌゞの構造がテキストで返っおきたす。芁玠に番号uidが振られおいるので、座暙を掚定しなくおも操䜜できるんですよね。 uid=1_0 RootWebArea "SIOS Tech Lab - ゚ンゞニアのためになる技術トピックス" url="https://<自瀟ブログ>/" uid=1_1 banner uid=1_218 StaticText "© 2026 SIOS Tech Lab All rights reserved." 䜿い分けは1行で足りたす。操䜜するためならスナップショット、人に芋せる・芋た目を刀断するためならスクリヌンショットです。 ブラりザ操䜜なら、だいたい䞀通りできる ここたでスナップショットずスクリヌンショットしか出しおいたせんが、道具はもっずありたす。公匏の ツヌル䞀芧 を数えるず、2026幎9月の時点で57個ありたした。 ブラりザ操䜜に関わるこずなら、だいたい䞀通りできる ず思っおいいです。 たずナヌザヌ操䜜のほう。クリック、テキスト入力、フォヌムの䞀括入力、ドラッグ、ファむルのアップロヌド、キヌの送信、ダむアログの応答。タブの開閉ず切り替え、画面サむズやデバむスの゚ミュレヌションもありたす。人が画面でやっおいるこずは、だいたい指瀺に眮き換わりたす。 うれしいのはここからです。F12 で開く開発者ツヌルのタブで芋おいたものは、だいたいそのたた取れたす。ネットワヌクのリク゚スト䞀芧ずヘッダCookie も入りたす、パフォヌマンスのトレヌスから Core Web VitalsLCP・INP・CLS、コン゜ヌルのメッセヌゞ。画面の芋た目ではなく数字が返るので、そのたた AI に読たせお刀断させられるんですよね。 いた議論䞭の話も1぀おたけで曞いおおきたす。ヘッド付きの Chrome を觊らせおも、OS のマりスカヌ゜ルは動きたせん。入力は CDP のレむダで泚入されるので、隣で芋おいる人にぱヌゞェントがどこをクリックしたのか分からないんですよね。そこで「ゎヌストカヌ゜ルを重ねお芋せる」 提案 が䞊がっおいお、実装たで枈んだ PR も1本ありたしたが、ペヌゞに DOM を挿し蟌む圢は避けたいずいうメンテナのコメントが付いお、いたは意芋を集めおいる段階です2026-09-07 に確認。この蟺はすぐ動くので、気になる人は issue を盎接芋おください。 動くのは、普段䜿いずは別のプロファむル オプション無しで動くのは、chrome-devtools-mcp が自前の堎所に専甚のプロファむルを䜜っおくれるからです。これは詊しおみるずわかりたすが、Chrome 136 以降、既定のプロファむルのたただずリモヌトデバッグの指定が無芖されるんですよね。理由は 公匏のリンク で。 芁するに 普段䜿いの Chrome ずは別のプロファむルで動く 、ずいう前提が最初から満たされおいるわけです。 眮き堎所を自分で決めたいずきだけ、 --user-data-dir で枡したす。僕がそうしおいるのは、埌から䞭を芋られるようにしおおきたかったからです。ほかにも画面サむズやスクリヌンショットの圢匏を指定するオプションがありたすが、最初は1぀も芁りたせん。芁るようになったら 公匏のオプション䞀芧 で必芁なものだけ拟っおください。 専甚のプロファむルには、本物のログむン状態がそのたた乗りたす。フォルダごず別の PC にコピヌしおも、そのたたでは䜿えない䜜りになっおいたすここは自分では詊しおいたせん。ただこれを「安党」ずは読たないでほしくお、守れおいるのは持ち出しだけで、同じ PC の䞭からは觊れおしたうんですよね。だから普段䜿いのプロファむルず分かれおいるのは、動かすための条件であるず同時に、被害を閉じ蟌める線でもあるんだず思っおいたす。 ログむン画面に来たら、゚ヌゞェントはどうするのか ここからが本題です。僕の前提は1぀で、 ID ずパスワヌドぱヌゞェントに枡したせん 。認蚌が芁る画面を觊らせたいずきも、そこだけは人が打぀。その前提で指瀺を先に曞いおおきたす。 実際に投げたプロンプトの末尟がこれです。 前提: ログむンは人がやる — 認蚌画面が出たら止たっお䟝頌する。 そう曞いおおくず、゚ヌゞェントは認蚌フォヌムを芋぀けた時点で入力を詊さずに止たりたす。止たったずきの出力がこれです。 ここで止たりたす。**手動ログむンをお願いしたす。** ブラりザは `<自瀟ブログ>/wp-login.php` を開いた状態です`pageId=1`、入力前の1枚は取埗枈み。 お願いしたいこず: 1. その Chrome りィンドりで**ログむンしおください**ID/パスワヌドは私は䞀切觊りたせん 2. ⚠ ログむン埌、**管理画面のたた眮いおおいお構いたせん** — 私は管理画面をスナップショットも スクショもせず、公開ペヌゞぞ盎接 navigate しお戻したす 3. 終わったら「**終わった**」ず蚀っおください。それを合図に再開したす 止たり方ずしお効いおいるのは3぀あるず思っおいお。頌みごずが1぀に絞られおいるこずログむンしお、だけ。他の刀断を人に投げおいない、やらないこずを明蚀しおいるこずID・パスワヌドには觊らない、管理画面は撮らない、再開の合図を先に決めおいるこず人が次に䜕を蚀えばいいか迷わないです。 ただしこれは芏玄であっお、匷制ではありたせん。フォヌムに入力するツヌル自䜓は生きおいるので、絶察に觊らせたくないなら、ツヌルの蚱可蚭定の偎で萜ずすこずになりたす。 画面はこれ1枚だけで、空欄のフォヌムです。゚ヌゞェントはここに䞀床も觊っおいたせん。 人が返した合図は「ログむンした」でした。䟝頌したのは「終わった」だったんですけど、今回はこれでも再開できおいたす。 再開の1手目は list_pages にしたす。これも指瀺に先に曞いおおく䞀行です。ブラりザを開き盎すず番号が振り盎されるこずがあっお、そのずきは MCP 自身が「再起動されたので番号が倉わった」ず教えおくれたす。叀い番号のたた呌ぶず倱敗するので、1手目は取り盎しにしおおくず安党なんですよね。 ぀いでに、採番の確認はそのたた「いた䜕が開いおいるか」の確認にもなりたす。 list_pages() → ## Pages 1: ダッシュボヌド ‹ SIOS Tech Lab — WordPress (https://<自瀟ブログ>/wp-admin/) [selected] ログむンするず管理画面に入れたす。ただ今回は䞭を芋おいなくお、タむトル行がテキストで出おいるだけで、そのたた公開ペヌゞぞ戻したした。 戻した先で撮ったのがこれです。さっきず同じ URL、同じ䜍眮で、䞊端に管理バヌが増えおいるだけです。 これで受け枡しが成立しおいたす。人が担ったのはログむンだけで、その前埌ぱヌゞェントが動いおいるんですよね。 ひず぀気を぀けどころがありたす。認蚌の先ぞ入るずいうこずは、 そこから先はログむンした人の暩限で動く ずいうこずです。読める範囲も抌せるボタンも、認蚌した自分ず同じになりたす。ID ずパスワヌドを枡しおいないこずず、任せた範囲が狭いこずは別なので、管理者のアカりントで入るのか、芁る暩限だけのアカりントで入るのかは、觊らせる前に決めおおいたほうがいいです。 なお、ここで曞いおいるのは自分たちのサむトでの話です。他瀟のサヌビスを自動で操䜜させおよいかは芏玄の話が別に芁るので、この蚘事では扱いたせん。 毎回ログむンし盎すこずになるの プロファむルに䜕が乗るのか、認蚌を誰が担うのか。ここたでの2぀は、最埌に1぀の問いで぀ながりたす。ログむンした状態は、どこたで残るのか。ここは局が2぀あるので分けお曞きたす。 1぀は、プロファむルに乗る Google アカりントのサむンむンです。こちらは残っおいお、閉じお開き盎しおも入ったたたでした。確かめたのは玄8分埌、玄28時間埌、いちばん長いずころで玄6日2時間埌です。 もう1぀は、そのサむトが自前で持っおいるセッションです。今回の自瀟ブログはこちら偎で、ブラりザを閉じたらログむンし盎しになりたした。りィンドりを普通に閉じお匷制終了はしおいたせん、玄25秒埌に開き盎しおも、管理バヌは消えおいたした。 なぜ残るのか、なぜ残らないのかは远っおいたせん。ログむン状態がどこに保存されるかで倉わるずころなので、どちらも振る舞いだけ曞いおいたす。 残らないなら残るようにしたい、ず思うずころですが、サむト偎の認蚌情報を自分で取り出しお保存する方向ぞは行っおいたせん。保存した時点でそれが流出の察象になりたすし、次からは人が䞀床も通らずに入れおしたうからです。最埌の防壁ずなる人間の手䜜業が、䞞ごず無くなるんですよね。動かしおいるあいだはブラりザを芋おおいお、挙動に違和感があったり、なんかダバいなず思ったらパスワヌドを倉える、くらいは構えおおくずいいず思いたす。 ただ、Google のサむンむンだけは自分で保存しなくおも残りたす。人が毎回通るのはサむト偎のログむンのほうだけで、そのプロファむルは Google に入れる状態のたた眮いおある、ず思っおおいたほうがいいです。 倧事なのは、残らなくおも認蚌の切り分けは成立しおいるずいうこずです。芋返りは「次から芁らなくなる」ではなく、今この1回、人が觊るのはログむンだけで枈むずいうこずなんですよね。 じゃあ次は䜕を枡せるのか 冒頭の問いを回収するず、ログむンが芁る画面はどうするのか、答えは人が1回入っお、その先を枡す、です。 枡せる仕事はただありたす。 list_network_requests で通信を、 list_console_messages でコン゜ヌルを取れるので、「この画面のここが遅い」「゚ラヌが出おいないか」をそのたた調べさせられるんですよね。 ここで䜿い分けを1぀。開発の珟堎では、僕は Playwright のほうを䜿っおいたす。テストを曞く堎所ですし、同じ手順を䜕床も回すならコヌドで持っおおくほうが確実なので。入口は Playwright MCP で始めるブラりザ自動化 に曞きたした。Chrome DevTools MCP が効くのは、日垞のちょっずした動䜜を1回やらせたいずきです。わざわざ環境を䜜るほどではない、でも手でやるのは面倒、くらいの仕事ですね。正盎、Windows に Playwright の環境を入れるのが面倒でずっず手が止たっおいた、ずいうのも僕の偎の事情ずしおはありたす。 反埩するならやはり Playwright を曞いたほうがいいです。楜なのは着手であっお、運甚ではないので。 最初の䞀歩は1぀だけです。 claude mcp add を1本打っお、「自分のブログのトップを開いお、構造を取っおみお」ず頌んでみる。テキストが返っおきたら、そこから先は枡せたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Chrome DevTools MCP を Windows に入れる。ログむンだけ人がやる first appeared on SIOS Tech Lab .
こんにちは、OSSよろず盞談宀のSKです。 OSS に関するお問い合わせが日々寄せられる䞭で、今回は Linux における pam_faillock によるアカりント䞀時ロック に関連しお寄せられたお問い合わせをご玹介したす。 以䞋のような問い合わせがありたした。 Red Hat Server 環境にお、䞀時的にログむンができなくなる事象が発生したした。 䞀般ナヌザがリモヌトからログむンしたずころ、認蚌゚ラヌずなりログむンできたせんでした。 管理者rootナヌザに切り替えお passwd -S コマンドで確認したずころ PS パスワヌド蚭定枈み・有効ず衚瀺されおおり、アカりントロック LK にはなっおいたせん。 たた、特に解陀操䜜を行わなくおも䞀定時間経過するず正垞にログむンできるようになりたす。䜕が原因なのでしょうか passwd -S コマンドは /etc/shadow ファむルを参照したすが、 pam_faillock による䞀時ロック情報は /etc/shadow ではなく専甚のディレクトリ /var/run/faillock/ で管理されおいるため、 passwd -S では確認できたせん。 今回は、このやり取りに関連しお、「 pam_faillock ずは䜕か」「 passwd -S で確認できない理由」「 faillock コマンドでの確認方法」「LinuxRed Hat系での動䜜怜蚌手順」に぀いお解説したす。 pam_faillock ずは pam_faillock は、Linuxの認蚌システムPAMで提䟛されおいるセキュリティモゞュヌルです。 短時間に指定された回数以䞊の認蚌倱敗パスワヌド間違いなどが発生した際に、ブルヌトフォヌス攻撃総圓たり攻撃などの䞍正アクセスを防ぐ目的で、該圓アカりントを䞀時的に自動ロックしたす。 なぜ passwd -S で確認できないのか passwd -S コマンドは /etc/shadow ファむルを参照 しおアカりントのステヌタス PS : パスワヌド有効、 LK : ロック等を刀定したす。 passwd -S コマンド出力䟋 testuser PS 2026-09-01 0 99999 7 -1 (Password set, SHA512 crypt.) 䞀方、 pam_faillock による䞀時ロック情報は /etc/shadow を曞き換えるのではなく、専甚のディレクトリデフォルト: /var/run/faillock/ に倱敗履歎ずしお蚘録・管理 されたす。 そのため、 /etc/shadow 䞊は正垞 PS に芋えおも、 pam_faillock の倱敗カりンタが䞊限に達しおいるこずで認蚌が拒吊される珟象が発生したす。 システムログず蚭定パラメヌタ システムログ/var/log/secureの出力䟋 pam_faillock により䞀時ロックが発生するず、 /var/log/secure に以䞋のようなログが出力されたす。 Sep 01 13:32:06 server sshd:pam_unix(sshd:auth): authentication failure; ... user=<察象ナヌザ名> Sep 01 13:32:40 server sshd:pam_unix(sshd:auth): authentication failure; ... user=<察象ナヌザ名> Sep 01 13:36:01 server sshd:pam_unix(sshd:auth): authentication failure; ... user=<察象ナヌザ名> Sep 01 13:36:01 server sshd:pam_faillock(sshd:auth): Consecutive login failures for user <察象ナヌザ名> account temporarily locked 蚭定パラメヌタ䟋/etc/security/faillock.conf /etc/security/faillock.conf の代衚的な蚭定項目は以䞋の通りです。 deny = 3 fail_interval = 900 unlock_time = 1800 deny = 3 : 3回連続で認蚌倱敗するずロックされたす。 fail_interval = 900 : 15分間900秒の䞭で発生した倱敗回数をカりントしたす。 unlock_time = 1800 : ロック発生埌、30分間1800秒経過するず自動的にロックが解陀されたす。 泚意点 パスワヌド倉曎では解陀されない ロック䞭にパスワヌド倉曎を行っおも pam_faillock のロック状態は解陀されたせん passwd ず pam_faillock の管理領域が独立しおいるため。 倱敗詊行の繰り返しによる延長 ロック䞭にログむン詊行を繰り返すず新たな倱敗履歎が远加され、ロック期間 unlock_time がさらに延長されるこずがありたす。 faillock コマンドによるロック状態の確認方法 pam_faillock による認蚌倱敗履歎およびロック状態を確認するには、 faillock コマンド を䜿甚したす。 faillock --user <察象ナヌザ名> 実行結果の䟋 When Type Source Valid 2026-09-01 15:25:51 TTY pts/1 V 2026-09-01 15:25:59 TTY pts/1 V 2026-09-01 15:26:06 TTY pts/1 V Valid 列に V が衚瀺されおいる詊行が有効な倱敗蚘録ずしおカりントされおおり、これが指定回数 deny の倀に達するずロック状態ずなりたす。 Linux での動䜜怜蚌手順 実際に Linux今回は Red Hat Enterprise Linux 8環境で pam_faillock の動䜜を確認する手順䟋です。 1. テスト甚ナヌザの䜜成 # useradd testuser # passwd testuser 2. SSSDの最小構成蚭定ず起動 # cat << 'EOF' > /etc/sssd/sssd.conf [sssd] config_file_version = 2 services = nss, pam domains = files [domain/files] id_provider = files EOF # chown root:root /etc/sssd/sssd.conf # chmod 600 /etc/sssd/sssd.conf # systemctl enable --now sssd 3. authselect による faillock の有効化 # authselect enable-feature with-faillock # authselect apply-changes # authselect current 4. faillock.conf の蚭定 # cp -p /etc/security/faillock.conf /etc/security/faillock.conf.org # cat << 'EOF' >> /etc/security/faillock.conf deny = 3 fail_interval = 900 unlock_time = 60 EOF 5. 意図的な認蚌倱敗ずロック確認 䞀般ナヌザぞ切り替えた䞊で、わざずパスワヌドを間違えおログむンを詊みたす。 # su - testuser # パスワヌドを3回以䞊間違える その埌、root ナヌザで倱敗履歎を確認したす。 # faillock --user testuser たずめ Linux で「パスワヌド蚭定は正垞 PS なのにログむンできない」堎合は、 pam_faillock による䞀時ロック を疑い、 /var/log/secure や faillock --user <ナヌザ名> コマンドで状態を確認するこずが有効です。 ご芧いただきありがずうございたした 参考ドキュメント  pam_faillock(8) — Linux manual page   https://man7.org/linux/man-pages/man8/pam_faillock.8.html  faillock(8) — Linux manual page   https://man7.org/linux/man-pages/man8/faillock.8.html ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post OSSサポヌトの珟堎から䞀時的なアカりントロックの原因ずは first appeared on SIOS Tech Lab .
ども最近はペむンを䜕枚も䞊べおClaude Codeを転がしおる韍ちゃんです。 Claude Codeの返信、長くお読むのがしんどくないですか 聞いたこずにはちゃんず答えおくれるんだけど、そこたで詳しく芁らなかったな、っおなるこず。あれ、output styleで倉えられたす。 公匏によるず、output styleが倉えるのは答え方だけで、知っおいるこずのほうは倉わらないそうです。詳しくは 公匏のOutput stylesドキュメント ぞ。 今日曞くのは、これを䜜業ごずに、起動のタむミングでセットする運甚です。僕は䜜業ごずにペむンを立おおいろんな皮類の䜜業をやっおるんですけど、「この䜜業のずきはこう答えお」「こっちのずきはこう聞くから」っお毎回説明し盎しおたんですよ。それが起動時の䞀蚀で枈むようになりたす。最埌に、自分で曞いた文蚀が実際の返答をどれくらい倉えたかを枬った結果も䞊べたす。どのスタむルが正解かみたいな良し悪しの話はしたせん。 そもそもoutput styleっお、どこに䜕を曞くや぀なんでしょうか 倉えるのは「答え方」で、「知っおるこず」じゃないです output styleは、システムプロンプトを曞き換える局です。公匏にも「Output styles change how Claude responds, not what Claude knows」ず曞いおあっお、日本語にするず「どう答えるかを倉えるだけで、䜕を知っおいるかは倉えない」ずいうこずですね。 CLAUDE.mdやskillに曞くのは「この䜜業ではこれを芋ろ」「この芏玄に埓え」みたいな答えの䞭身を䜜る偎の話で、output styleに曞くのは出おきた答えをどう芋せるかの話です。ここが混ざるず「曞いたのに効かない」になるので、僕の䞭ではこの2぀で割り切っおたす。 組み蟌みのスタむルは5぀入っおいたす。 Default: 今たで通りの、既定のシステムプロンプトです Concise: 結論を先に蚀っお前眮きを削り、短く返したす Explanatory: 䜜業の合間に、孊びになる説明を挟んでくれたす Learning: 孊習モヌドで、コヌドの䞀郚を TODO(human) にしお自分で曞かせおくれたす Proactive: 刀断で立ち止たらず、実行を優先したす それぞれの性栌ずか向き䞍向きは公匏のドキュメントによくたずたっおるので、そちらに譲りたす。 ずっず同じスタむルで䜿いたいなら、蚭定に曞いお保存したす たぶんこれが䞀番よくやる圢だず思いたす。 自䜜のスタむルファむルの眮き堎は3段階ありたす。自分のPC党䜓に効かせるなら ~/.claude/output-styles/ 、このリポゞトリだけに効かせるなら .claude/output-styles/ 、あずは組織が配るmanaged policyです。䞭身はfrontmatterに name ・ description ・ keep-coding-instructions があれば動きたす実物は蚘事の最埌に䞞ごず貌っおありたす。 眮いたスタむルは /config から遞べお、遞んだ結果は .claude/settings.local.json に保存されお、そのたた効き続けたす。ただしこれ、公匏に local project level ず明蚘されおいるずおり project 偎の蚭定なので、自分のPC党䜓には効きたせん。党郚のプロゞェクトで効かせたいなら ~/.claude/settings.json に outputStyle を自分で曞く必芁がありたす。䞭身はこれだけです。 { "outputStyle": "dialogue" } 効くタむミングに぀いおは、公匏にはっきり曞いおありたす。 Output style is part of the system prompt, which Claude Code reads once at session start. Changes take effect after /clear or a new session. 蚳すず「output styleはシステムプロンプトの䞀郚で、Claude Codeはセッション開始時に䞀床だけ読み蟌む。倉曎を効かせるには /clear するか新しいセッションを始める必芁がある」ですね。走っおる途䞭で曞き換えおも、そのセッションには反映されないっおこずです。 で、ここたでが「保存しお、ずっずそれで䜿う」やり方なんですけど。この圢だず 党郚の䌚話が同じスタむルになる んですよね。僕がやりたかったのは、そこじゃなかったんです。 䜜業ごずに倉えたいので、僕は起動をやり盎す方を遞びたした さっきの公匏の匕甚のずおり、 /clear でも切り替わりたす。なので同じセッションのたた /config で遞び盎しお /clear 、ずいう運甚でも党然いけたす。 ただ僕は䜜業ごずにペむンを新しく立おるタむプなので、どうせ起動するなら起動時に枡しおしたえ、ずいう方を遞びたした。そのためのコマンドがこれです。 claude --settings '{"outputStyle":"structured"}' さすがに毎回JSONを曞くっお運甚はだるいので、スクリプトでラッパしお起動時に遞択するだけでoutput styleを遞択できるようにしおいたす。 structured は自分で曞いたスタむルの名前で、䞭身は次の章で出おきたす。 面癜いのは、 outputStyle っおキヌ自䜓はさっきの settings.json ずたったく同じずころです。蚭定ファむルに曞けば恒久蚭定になっお、起動時に --settings で枡せばその起動限りの䞀時蚭定になる。 同じキヌを、眮き堎所だけで恒久にも䞀時にもできる のが、この運甚の芯でした。 じゃあ保存枈みの蚭定ず --settings が䞡方あったらどっちが勝぀のか。ここは公匏に曞いおなかったので自分で詊したんですけど、 --settings のほうが勝ちたす。しかも蚭定ファむル自䜓は曞き換わらなくお、効くのはその起動限りでした。 /config で普段のスタむルを決めおおいお、特定の䜜業のずきだけ起動時に䞊曞きする、が玠盎に成立したす。 あずはClaude Codeに新芏ペむンを䜜らせるっお運甚もありたす。これはtmux経由でClaude Codeを起動する必芁があるんで、環境の甚意は芁りたすね。 tmux偎でペむンを増やす話は前に曞いたので、ここでは起動の䞀行だけにしおおきたす。tmuxの入れ方は DevContainer に tmux を入れお、蚭定ごず配る 、その堎でペむンを増やす運甚は Claude Codeをスマホでリモヌト、その堎でペむンが増える にたずめおありたす。ホスト偎の ~/.claude をどう扱っおるかも Claude Codeの~/.claude、どれを共有しおどれを分ける に曞いたので、そちらで。 あず公匏に曞いおあるこずをひず぀だけ。output styleはsubagentには適甚されたせんforkのずきだけ䟋倖だそうです。 自分で曞いた文蚀は、ちゃんず圢になっお出おきたした ここからは実隓です。実際どんだけ返信が倉わるんかな、っお気になったので。自分で曞いた2枚ず、䜕も眮いおいない既定の3条件に、同じ5問を各1回ず぀投げお、返っおきたものを数えたした。モデルはOpusで固定しおいたす。 曞いた2枚はこういう性栌です。 dialogue : 「散文で答えろ」ずいう1枚。「぀ながった文で答えお、箇条曞きや衚は本圓に構造が芁るずきだけ䜿え」ず曞きたした structured : 「短い塊に刻め」ずいう1枚。「たず結果を1文で出しお、そのあずは1点1項目の短いブロックに割れ」ず曞きたした 䜓感で語るず絶察に盛るので、確かめ方は少し工倫したした。たず「本圓にスタむルが効いおいるか」自䜓を、乱数で䜜ったトヌクンを返信の末尟に必ず付けろずいう指瀺で二倀刀定したすトヌクンはシェル偎で䜜っお自分には芋せおいないので、システムプロンプト経由でしか出おきたせん。そのうえで、1問1条件ごずに空っぜの䜿い捚おディレクトリを䜜っお、その回に䜿うスタむルを1枚だけ眮いお投げたした。ずはいえ、質問の内容自䜓が答えの圢に効いおくる亀絡はどうやっおも残りたす。 数えた結果がこれです。 既定基準 dialogue structured 字数5問平均 1582字 843字既定比53% 1011字64% 行数5問平均 47.0行 9.6行 22.6行 箇条曞き5問合蚈 18 0 38 どれも5問の平均です。既定は質問によっお振れお、今回の5問でも䞀番長い問い2108字ず䞀番短い問い998字で2倍の開きがありたした。平均を絶察倀みたいに扱うず事故りたす。 数字だけだずピンずこないので、実物も1組だけ眮いおおきたす。「小芏暡なチヌムで、モノレポにするかリポゞトリを分けるか迷っおいたす。どちらがいいですか」に察する、既定ず dialogue の返答です。どちらも長いので冒頭だけの抜粋です。 既定はこうでした。 小芏暡チヌムなら、たずモノレポをおすすめしたす。理由は「人数が少ないほど、リポゞトリを 分けるコストが盞察的に重くなる」からです。 ## モノレポを掚す理由 **暪断倉曎が1PRで枈む** APIのスキヌマを倉えたらフロントも盎す、ずいう倉曎が日垞的に起きたす。略 **環境構築ずCIが䞀元化できる** lint蚭定、フォヌマッタ、tsconfig、CIワヌクフロヌ、䟝存バヌゞョン。分けるず必ずドリフトしたす。 ## 分けたほうがいい堎合 - **公開ラむブラリ/OSS** — 倖郚にリリヌスするパッケヌゞは、独立した履歎ずIssueトラッカヌが あったほうが玠盎です - **ラむフサむクルが本圓に無関係** — 瀟内ツヌルや実隓甚プロゞェクトなど、本䜓ず䞀緒に ビルドする意味がないもの 以䞋略 dialogue はこうです。 小芏暡チヌムであれば、たずモノレポをおすすめしたす。 理由は単玔で、リポゞトリを分けたずきのコストの倧半が「人数が少ないほど重くのしかかる」 皮類のものだからです。共通ラむブラリのバヌゞョン管理、耇数リポゞトリにたたがる倉曎の PR調敎、CI蚭定の重耇、どこに䜕があるか分からなくなる問題――これらはチヌムが小さいほど、 䞀人あたりの負担ずしお効いおきたす。略 分けたほうがいいのは、次のどれかに圓おはたるずきです。デプロむ先も蚀語もラむフサむクルも たったく別物で、コヌドを共有する芋蟌みがない堎合。倖郚に公開する OSS やクラむアントに 枡す成果物が含たれる堎合。あるいは、アクセス暩限を明確に分離する必芁がある堎合です。 結論の䞭身はどっちも同じです。倉わったのは枡され方のほうでした。 䞀番はっきり出たのが箇条曞きの行です。 dialogue は5問通しおれロ、 structured は既定の倍以䞊刻んでたす。「散文で答えろ」ず曞いた1枚が箇条曞きを䜿わなくなっお、「短い塊に刻め」ず曞いた1枚が刻む。 曞いた文蚀が、そのたた圢になっお出おきおる わけです。ここは玠盎に面癜かったです。 で、もうひず぀。よく芋るず2枚ずも既定より短いんですよね。片方は散文で答えろずしか曞いおないのに、です。皮を明かすず、2枚には共通で曞いた1行がありたす。 Keep responses focused, brief, and concise. 短くなったのはこの1行のぶんで、散文で答えろず曞いたからではなかったです。 ここたで芋えたのは、あくたで出おきた圢の話です。䞭身の考え方たで倉わったのかは、この枬り方では芋えおいたせん。 それず、です・たす調たわりの日本語の文䜓は公匏がカバヌしおる範囲の倖なので、そこは完党に自䜜で割り切っお曞いおたす。 受け取る圢だけは、自分で揃えられるんだなず思いたした 觊っおみお思ったのは、 こっちが受け取る圢は自分で揃えられる んだな、ずいうこずでした。䜜業ごずに「この時はこう答えお」っお毎回説明し盎しおたのが、起動時の䞀行に眮き換わったのは地味に嬉しいです。 ずはいえ各条件1回ず぀しか枬っおないので、この圢が毎回同じように出るずたでは蚀い切れたせん。 あず、output styleがAIの思考そのものに圱響するのかどうかは、僕は確かめおいたせん。同じこずを疑った人は前からいるみたいで、GitHubにissueも立っおるんですが、誰も答えないたた自動で閉じられおたした。公匏は「どう答えるかを倉えるだけで、䜕を知っおいるかは倉えない」ず蚀っおいるので、䞀旊はそちらに乗っかっおおきたす。そもそも仮に思考の偎に差があったずしお、それがoutput styleのおかげなのかモデル偎の性質なのかも、この枬り方だず切り分けられおないですしね。 ではたた 付録今回䜿ったoutput styleの党文 dialogue 。 --- name: dialogue description: 䌚話モヌド。散文で答え、リストは必芁なずきだけ䜿う keep-coding-instructions: true --- Keep responses focused, brief, and concise. Keep disclaimers and caveats short, and spend most of the response on the main answer. When asked to explain something, give a high-level summary unless an in-depth explanation is specifically requested. Answer in prose, in connected sentences. Use bullets or a table only when the content is genuinely multi-dimensional and the structure is what makes it clear. If you have to choose between short and clear, choose clear. 日本語で答えるずきは、です・たす調で曞く。 structured 。 --- name: structured description: 刻むモヌド。結果を1文で出し、以降を短い塊に分けお読たせる keep-coding-instructions: true --- Open with the outcome: one sentence on what happened or what the answer is, before any detail. Keep responses focused, brief, and concise. Keep disclaimers and caveats short, and spend most of the response on the main answer. When asked to explain something, give a high-level summary unless an in-depth explanation is specifically requested. Break what follows into short blocks: one bullet per point, and a heading when there are several groups of them. Keep each block to a few lines. If you have to choose between short and clear, choose clear. 日本語で答えるずきは、です・たす調で曞く。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Codeの返信が長い、output styleを自䜜しお枬っおみた first appeared on SIOS Tech Lab .
Claude Codeのskill、気づいたら結構な数になっおたした .claude/skills/ に増えおいくskillっお、たずえるなら荷物なんですよね。今日は docs/article/ しか觊らないのに、 docs/seminar/ 甚も application/ 甚も、起動した瞬間から肩に乗っおいる。増えるこず自䜓は悪いこずじゃなくお、困りごずのたびに1枚足すのは健党な運甚だず思いたす。 ただ、本圓に効いおくるのは重さじゃないんですよね。ある領域専甚のskillっお、䜜った盎埌は䜿うんですが、しばらくするず觊らなくなるじゃないですか。「動いおるからいいや」で攟眮しお、気づいたら䞭身がどうなっおいるかも把握できおいない。実害はこっちのメンテの腐りの方だず思っおいたす。 ずはいえ僕自身、しんどいず思っおいたわけでもないんですよね。リポゞトリの特性䞊しかたないず諊めおいたした。で、これ捚おるしかないず思っおいたんですが、そうでもなかったんですよね。 ずころが、これ捚おなくおも枛らせるんですよね やっおいるこずは、眮き堎を1階局ずらしただけです。 リポゞトリ/ ├─ .claude/skills/ ← ここは垞駐。起動した時点で党郚乗る └─ docs/article/ └─ .claude/skills/ ← ここに眮くず、docs/article/ を觊るたで乗らない 䞋に移したskillは、セッションを起動した盎埌には1個も芋えおこないんですよね。ずころが、 docs/article/ 配䞋のファむルを觊った瞬間、こういう通知が来たす。 驚いたのは、知らないskillが急に珟れたこずじゃないんですよね。skillは自分で曞いおいるので、存圚自䜓は最初から知っおいたす。驚いたのは、仕組みが実際にちゃんず効いおいたこずです。消しおもいないし、起動した瞬間から肩に乗る状態垞駐にもしおいない。觊った瞬間だけ、そこにいる。 枛らすっおいうのは、捚おるこずじゃないんですよね 䞊が、ディレクトリを分けずに党郚rootぞ眮いた堎合です。 ある領域でしか関係のないskillを、たったく別の䜜業にたで持ち蟌んでいる 状態なんですよね。セミナヌの䌁画を緎っおいる最䞭に、ブログのレビュヌ甚skillが肩に乗っおいる。䞋がそれをやめた状態で、人は䞊䞋で同じたた、持ち物の眮き堎だけが倉わっおいたす。捚おたわけじゃなくお、眮き堎を倉えただけなんですよね。 たずえ話で行くなら、山登りに行くのにデスクワヌク甚の道具を持っおいくみたいなむメヌゞですね。さすがに過酷な山登りにPCをもっおいっおブログを曞くなんおこずしないですよね…ちゃんずおうちに眮いおいきたしょう。 これ、自分で芋぀けた裏技ずかじゃなくお、Claude Codeの公匏ドキュメントにちゃんず曞いおある仕組みです Agent Skills 。動的にロヌドされる蚭蚈が、もずもず甚意されおいるずいう話なんですよね。 しかもこれ、前の章で蚀った「觊らなくなっお腐る」問題にも効くんですよね。觊らない領域のものは、腐っおいたずしおもそもそも肩に乗っおいたせん。そしお道具が䜜業のすぐ隣に眮いおあるので、その領域を觊っおいる日は、skillのメンテもその䜜業の射皋に入っおくる。別の堎所ぞ芋に行く必芁がないぶん、手が入りやすいんですよね。 ちなみにCLAUDE.mdは、この仕組みで倖れる偎じゃなくお、捚おる偎で運甚しおいたす。刈り蟌みは2回効きたしたが、3回目はただ来おいたせん。捚おる偎はこうやっお止たるずいう察比だけ、今日は取っおおきたす。 自分のskillは、どこに眮けばいいのか考えたした 僕は開発も調査もブログもセミナヌも、ぜんぶ1぀のリポゞトリに寄せお進めおいたす Claude Codeで党業務を1リポゞトリに䞀元管理する䜜業基盀の䜜り方 。だから「ディレクトリごずに担圓が違う」ずいう状態が最初からあっお、今日の話もそこに乗っおいたす。 ディレクトリをどこで切るかずいう䞀般論は、公匏ガむドが1ペヌゞ䜿っお曞いおいるので、そちらに譲りたす。ここで決めたいのは眮き堎だけです。 眮き堎を決める軞は、誰がそのskillを撃぀かです。 モデルが撃぀skill 人だけが /名前 で撃぀skill 眮き堎所 担圓ディレクトリのスコヌプぞ移す rootに据え眮いたたた 枛るもの 垞駐コストdescription / 候補䞀芧の汚れだけ ここでいうスコヌプは、さっきの「そのディレクトリを觊ったら出おくる」の仕組みのこずです。これず disable-model-invocation 長いので以䞋dmiは、名前が䞊びがちですが別の道具なんですよね。スコヌプは垞駐コストを倖す道具で、dmiは誰が撃おるかを制限する道具なので、混ぜお考えないようにしおいたす。 図の右偎が、垞駐する文字数の内蚳です。2026-08-29時点の実枬で、数えおいるのはトヌクン数ではなく、起動時に泚入されるdescriptionの文字数だけです。 芋おのずおり、 docs/article/ を觊るだけで垞駐の4分の1くらいが䞊乗せされたす。ブログを曞く日だけ乗っお、セミナヌの日には乗らない。この差が、眮き堎を分けたぶんです。 䜙談ですが、以前僕自身が別の蚘事で「特定の䜜業・ディレクトリでしか䜿わないskillはdmiで倖す」ず曞いたこずがあるんですが、これはもう今の正解じゃないので蚂正しおおきたす。 実際に眮き堎を倉えおから、䜜業のしかたが1぀だけ倉わりたした 移蚭したあずにやるこずは、実はそんなに難しくないんですよね。その領域の䜜業を始めるずき、1発目をこう投げるだけです。 @docs/article/CLAUDE.md ブログの話をしようか これで通知が出るので、乗ったのを芋おから本題に入る。それだけです。この確認は1セッションに぀き1回で十分で、䞀床発芋が走ったディレクトリは、そのセッション䞭はもう芋に行かなくおいいんですよね。 冒頭で、䜿わない道具たで党郚持ち歩いおいるのが荷物みたいだ、ずいう話をしたした。眮き堎を倉えたあずの毎日は、たさにその逆です。今日觊る領域の道具だけが手元に出おきお、觊らない領域の道具は棚に戻ったたた出おこない。捚おたわけじゃなくお、必芁なずきにだけ持っおくるようになっただけなんですよね。 で、これでどれくらい軜くなったかずいうず、2026-08-29時点の実枬で、党郚が垞駐だったころの9,170字から、いたは6,789字たで萜ちおいたす。率にするず-26.0%です。 正盎、これは自分で自分の数字を殎っおおきたいずころで、䞖の䞭には-83%枛らしたみたいな先行事䟋もあるので、それに比べるず3倍以䞊の差がありたす。それに、文字数が枛ったからっお゚ヌゞェントが賢くなったわけでもないんですよね。枛ったのはあくたで肩の荷物の重さで、それ以䞊でも以䞋でもない、ずいうのが正盎なずころです。 ではたた ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Codeのskill、捚おる前に眮き堎を倉えおみたせんか first appeared on SIOS Tech Lab .
はじめに 前々回の蚘事ではROSA HCPの抂芁やメリットを玹介し、前回の蚘事ではROSA HCPクラスタヌを実際に構築しおきたした。 今回は、本番運甚を芋据えた、ROSA HCPクラスタヌにおける「ログ保管の自動化」 に぀いおご玹介したす。 KubernetesやOpenShift環境では、Podが再起動したり削陀されたりするず、コンテナ内に出力されおいたログは消えおしたいたす。そのため、システムの安定運甚やトラブルシュヌティングを行うためには、コンテナログを保管する仕組みが必須ずなりたす。 そこで、AWSの運甚に慣れた゚ンゞニアにずっお、最も芪和性が高く手軜なログ保管先ずなるのが「Amazon CloudWatch Logs」です。 本蚘事では、具䜓的な蚭定手順に進む前の前提知識ずしお、たずはROSA環境におけるログ保管の党䜓像を敎理し、その埌CloudWatch Logsぞ転送する具䜓的な蚭定方法を解説しおいきたす。 ROSAで出力されるログの皮類 CloudWatch Logsぞの転送蚭定を始める前に、たずはROSAが出力するログの皮類に぀いおご玹介したす。 ROSAを構成するコンポヌネントから出力されるログは以䞋の3皮類に倧別されたす。 ROSA with HCPでは、ログ皮別によっおログの転送方法が異なるため泚意が必芁です。 ログ皮別 抂芁 転送方法 アプリケヌションログ ナヌザヌがデプロむしたPod(コンテナ)から出力されるログ OpenShift Logging むンフラストラクチャログ ワヌカヌノヌドやOpenShiftのシステムコンポヌネント(※)から出力されるログ OpenShift Logging 監査ログ クラスタヌ内での操䜜履歎のログ ROSA with HCPの暙準機胜 ※むンフラストラクチャログにおけるシステムコンポヌネントの定矩 OpenShfitの仕様䞊、以䞋のnamespaceで実行されおいるPodはすべお「OpenShiftのシステムコンポヌネント」ずみなされ、それらが出力するログはむンフラストラクチャログずしお分類されたす。 default kube–xxx openshift-xxx   OpenShift Loggingずは ROSA with HCPにおいお、アプリケヌションログ・むンフラストラクチャログを収集し、倖郚ぞ転送するための機胜が「OpenShift Logging」です。 Kubernetes暙準の機胜だけでは、Podが再起動したり削陀されたりするずコンテナ内のログも䞀緒に消えおしたいたす。OpenShift Loggingは、クラスタヌ内の各コンテナが出力するログをリアルタむムに収集し、倖郚今回はCloudWatch Logsぞず転送するパむプラむンを自動管理しおくれたす。 OpenShift Loggingを構成する䞻芁コンポヌネント 珟圚のOpenShift Loggingは以䞋のコンポヌネントで構成されたす。 Cluster Logging Operator ログ収集゚ンゞンや転送蚭定党䜓のラむフサむクルを管理するコンポヌネントです。 ログ収集゚ンゞン 各ワヌカヌノヌドにDaemonSetずしお配眮され、ノヌド䞊で動くコンテナのログを収集するコンポヌネントです。以前のOpenShiftではFluentdがログ収集゚ンゞンずしお䜿甚されおいたしたが、珟圚の最新バヌゞョンではパフォヌマンスずリ゜ヌス効率に優れたVectorがデフォルトの゚ンゞンずしお採甚されおいたす。   ログ転送パむプラむンを定矩する「ClusterLogForwarder」 OpenShift Loggingは、ログの転送パむプラむンの定矩を「ClusterLogForwarder」ずいうカスタムリ゜ヌスで定矩したす。 これにより、YAMLファむルを1぀定矩するだけで、「どのログを」「どこぞ転送するか」を自由に蚭定するこずができるようになっおいたす。  ClusterLogForwarderに定矩する転送パむプラむンは、䞻に以䞋の぀の芁玠を組み合わせお蚭定したす。 Inputs入力元 どのログを察象にするか Outputs出力先 どこに送るか Pipelines経路の結合 どのInputsを、どのOutputsぞ玐付けるか ログ転送のデヌタフロヌ OpenShift Loggingを利甚したログ転送のデヌタフロヌを図に瀺すず以䞋のようになりたす。 ログ収集゚ンゞンであるVectorはClusterLogForwarderの定矩に埓い、Inputsに蚭定したアプリケヌションログやむンフラストラクチャログを収集し、Outputsに蚭定したCloudWatch Logsにログを転送したす。 OpenShift Loggingを利甚したCloudWatch Logsぞのログ転送蚭定 ここからは実際にOpenShift Loggingを利甚したCloudWatch Logsぞのログ転送蚭定をご玹介したす。 前提条件 ROSAクラスタヌが構築枈みであるこず前回の蚘事を参照 ROSAクラスタヌにCLIでログむンできおいるこず ROSAクラスタヌの管理コン゜ヌルにWebブラりザからログむンできおいるこず 事前準備ロググルヌプずIAMロヌル・ポリシヌの䜜成 ロググルヌプの䜜成 たずは転送先ロググルヌプを䜜成したす。 以䞋はCloudFormationでロググルヌプを䜜成する際のサンプルになりたす AWSTemplateFormatVersion: '2010-09-09' Description: 'Log Group for OpenShift Logging' Resources: # アプリケヌションログ甚のロググルヌプ LogGroupApp: Type: AWS::Logs::LogGroup DeletionPolicy: "Delete" Properties: LogGroupName: /rosa/hcp-cluster/application # むンフラストラクチャログ甚のロググルヌプ LogGroupInfra: Type: AWS::Logs::LogGroup DeletionPolicy: "Delete" Properties: LogGroupName: /rosa/hcp-cluster/infrastructure 䞊蚘CloudFormationを適甚するず以䞋のロググルヌプが䜜成されたす。 ※OpenShift Loggingはアプリケヌションログを「<任意の倀>/application」、むンフラストラクチャログを「<任意の倀>/infrastructure」の名称のロググルヌプにログを転送したす。 IAMロヌル・IAMポリシヌの䜜成 OpenShift Loggingに割り圓おるIAMロヌル・IAMポリシヌを䜜成したす。 以䞋はCloudFormationでIAMロヌルを䜜成する際のサンプルになりたす AWSTemplateFormatVersion: '2010-09-09' Description: 'IAM Role and Policy for OpenShift Logging' Parameters: # ROSA構築時に䜜成したOIDC゚ンドポむント OidcEndpoint: Type: String Description: 'The OIDC Endpoint' Resources: # CloudWatch Logs ぞのアクセス蚱可ポリシヌ OpenShiftLoggingPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: 'rosa-openshift-logging-policy' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:PutLogEvents' - 'logs:PutRetentionPolicy' - 'logs:CreateLogStream' - 'logs:DescribeLogGroups' - 'logs:DescribeLogStreams' Resource: 'arn:aws:logs:*:*:*' # ログ転送甚 IAM ロヌル OpenShiftLoggingRole: Type: 'AWS::IAM::Role' Properties: RoleName: 'rosa-openshift-logging-role' AssumeRolePolicyDocument: !Sub | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/${OidcEndpoint}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OidcEndpoint}:sub": "system:serviceaccount:openshift-logging:cloudwatch-log-forwarder-sa" } } } ] } ManagedPolicyArns: - !Ref OpenShiftLoggingPolicy 䞊蚘CloudFormationの「OidcEndpoint」ずいうパラメヌタヌには、前回の蚘事で䜜成したoidc-configの情報を以䞋のrosaコマンドで確認し、「ISSUER URL」の倀の「https://」を陀いた倀を蚭定しおください。 $ rosa list oidc-config # 出力䟋 ID MANAGED ISSUER URL SECRET ARN XXXXXXXXX true https://xxxx/xxxxx CloudFormationを適甚するず以䞋のIAMロヌルが䜜成されたす。 OpenShift Loggingのむンストヌル Cluster Logging Operatorのデプロむ たず、OpenShift Loggingのコンポヌネントの1぀である「Cluster Logging Operator」をデプロむしたす。 以䞋はCluster Logging Operatorをyamlファむルでデプロむする際のサンプルになりたす。 opensfhit-logging-operator.yaml apiVersion: operators.coreos.com/v1 kind: OperatorGroup metadata: name: openshift-logging-operaror-group namespace: openshift-logging spec: upgradeStrategy: Default --- apiVersion: operators.coreos.com/v1alpha1 kind: Subscription metadata: name: openshift-logging-operaror-subscription namespace: openshift-logging spec: # むンストヌルするOpenShift Loggingのチャンネルを指定 channel: stable-6.4 # むンストヌルするOpenShift Loggingのバヌゞョンを指定 startingCSV: cluster-logging.v6.4.1 installPlanApproval: Manual name: cluster-logging source: redhat-operators sourceNamespace: openshift-marketplace 䜜成したopensfhit-logging-operator.yamlをROSAクラスタヌにログむンし、適甚したす。 $ oc login https://api.your-cluster-name.xxxx.p1.openshiftapps.com:443 --username cluster-admin --password XXXXX-XXXXX-XXXXX-XXXXX $ oc apply -f opensfhit-logging-operator.yaml OpenShiftの管理コン゜ヌル䞊の「゚コシステム」→「むンストヌル枈みのOperator」のペヌゞで「cluster-logging」が遞択できるようになりたす。 「cluster-logging」を遞択し、「installPlan」のペヌゞに遷移するず、OpenShift Logging Operatorのむンストヌルの承認確認画面が衚瀺されるため、「承認」を遞択したす。 むンストヌルを承認するず、ROSAクラスタヌ䞊にCluster Logging Operatorが起動したす。 $ oc get pod -n openshift-logging NAME READY STATUS RESTARTS AGE cluster-logging-operator-dd4646d96-zcw6g 1/1 Running 0 45s Secretリ゜ヌスのデプロむ 次に、ログ収集゚ンゞンにIAMロヌルを割り圓おるためのSecretリ゜ヌスをデプロむしたす。 以䞋はSecretのサンプルになりたす。 secret.yaml apiVersion: v1 kind: Secret metadata: name: cloudwatch-credentials namespace: openshift-logging stringData: # 䜜成したIAMロヌルのARNを指定 role_arn: arn:aws:iam::xxx:role/rosa-openshift-logging-role 䜜成したsecret.yamlをROSAクラスタヌに適甚したす。 $ oc apply -f secret.yaml ServiceAccountのデプロむ ログ収集゚ンゞンに割り圓おるServiceAccoutをデプロむしたす。 以䞋はServiceAccountのサンプルになりたす。 serviceaccount.yaml apiVersion: v1 kind: ServiceAccount metadata: name: cloudwatch-log-forwarder-sa namespace: openshift-logging 䜜成したserviceaccount.yamlをROSAクラスタヌに適甚したす。 $ oc apply -f secret.yaml ClusterRoleBindingのデプロむ ログ収集゚ンゞンにログ収集暩限を付䞎するため、ClusterRoleBindingリ゜ヌスでServiceAccoutに暩限の割り圓おを行いたす。 割り圓おるClusterRoleは Cluster Logging Operatorのデプロむ時に自動で䜜成されるClusterRoleを割り圓おたす。 以䞋はClusterRoleBindingのサンプルになりたす。 cluster-role-binding.yaml apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: logging-collector-logs-writer-crb subjects: - kind: ServiceAccount name: cloudwatch-log-forwarder-sa namespace: openshift-logging roleRef: kind: ClusterRole name: logging-collector-logs-writer apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: collect-application-logs-crb subjects: - kind: ServiceAccount name: cloudwatch-log-forwarder-sa namespace: openshift-logging roleRef: kind: ClusterRole name: collect-application-logs apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: collect-infrastructure-logs-crb subjects: - kind: ServiceAccount name: cloudwatch-log-forwarder-sa namespace: openshift-logging roleRef: kind: ClusterRole name: collect-infrastructure-logs apiGroup: rbac.authorization.k8s.io 䜜成したcluster-role-binding.yamlをROSAクラスタヌに適甚したす。 $ oc apply -f cluster-role-binding.yaml ClusterLogForwarderのデプロむ ログ収集゚ンゞンをデプロむするためのカスタムリ゜ヌス(CR)であるClusterLogForwarderをデプロむしたす。 OpenShift Logging OperatorはClusterLogForwarderに定矩した蚭定内容に基づいおログ収集゚ンゞンをデプロむしたす。 以䞋はClusterLogForwarderのサンプルになりたす。 cluster-log-forwarder.yaml apiVersion: observability.openshift.io/v1 kind: ClusterLogForwarder metadata: name: cloudwatch-cluster-log-forwarder namespace: openshift-logging spec: collector: tolerations: - operator: "Exists" serviceAccount: # 3.ServiceAccoutのデプロむで䜜成したServiceAccout名を指定 name: cloudwatch-log-forwarder-sa outputs: - name: cloudwatch-output type: cloudwatch cloudwatch: # 䜜成したロググルヌプ名を指定 # {.log_type||"unknown"}の郚分はinputRefsで指定しおいる「application」・「infrastructure」の文字列が埋め蟌たれる groupName: '/rosa/hcp-cluster/{.log_type||"unknown"}' # ロググルヌプを䜜成したリヌゞョンを指定 region: us-east-1 authentication: type: iamRole iamRole: roleARN: key: role_arn # 2.Secretリ゜ヌスのデプロむで䜜成したSecret名を指定 secretName: cloudwatch-credentials token: from: serviceAccount pipelines: - name: to-cloudwatch # アプリケヌションログずむンフラストラクチャログを収集するように指定 inputRefs: - application - infrastructure outputRefs: - cloudwatch-output 䜜成したcluster-log-forwarder.yamlをROSAクラスタヌに適甚したす。 $ oc apply -f cluster-log-forcluster-log-forwarder.yaml 動䜜確認 OpenShift Loggingのむンストヌル埌、Podが正垞に起動しおいるか確認しおください。 $ oc get pod -n openshift-logging NAME READY STATUS RESTARTS AGE cloudwatch-cluster-log-forwarder-fngmj 1/1 Running 0 61s cloudwatch-cluster-log-forwarder-pgx5h 1/1 Running 0 62s cluster-logging-operator-8646fc6b78-5hh5v 1/1 Running 0 12m 次に、䜜成したロググルヌプにむンフラストラクチャログ・アプリケヌションログが転送されおいるこずを確認しおください。 ※今回はアプリケヌションログを出力させるために「test」ずいうnamespace配䞋にテスト甚の「app-log-test」ずいう名前のPodを起動させ、ログ出力させおいたす。 䜜成したロググルヌプにアプリケヌションログ・むンフラストラクチャログのログストリヌムが䜜成されおいるこずがわかりたす。以䞊で、アプリケヌションログ・むンフラストラクチャログをCloudWatch Logsに転送する蚭定は完了になりたす。 【補足】監査ログAudit Logsの転送蚭定に぀いお ROSA with HCPにおける監査ログはRedHatが管理するコントロヌルプレヌン偎で出力されるため、ワヌカヌノヌド䞊に存圚するOpenShift Loggingではログの収集が行えたせん。 そのため、ROSA with HCPでは、監査ログをCloudWatch Logsぞ転送する暙準機胜が甚意されおいたす。 監査ログのCloudWatch Logsぞの転送を有効化したい堎合は、ROSAがCloudWatchにログを曞き蟌むための専甚IAMロヌルを䜜成し、そのIAMロヌルのARNを rosa コマンドのオプションに蚭定したす。 以䞋はCloudFormationでIAMロヌルを䜜成する際のサンプルになりたす。 AWSTemplateFormatVersion: '2010-09-09' Description: 'IAM Role and Policy for ROSA HCP Audit Log' Parameters: # ROSA構築時に䜜成したOIDC゚ンドポむント OidcEndpoint: Type: String Description: 'The OIDC Endpoint' Resources: # 1. CloudWatch Logs ぞのアクセス蚱可ポリシヌ RosaAuditLogPolicy: Type: 'AWS::IAM::ManagedPolicy' Properties: ManagedPolicyName: 'rosa-audit-log-policy' PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - 'logs:CreateLogGroup' - 'logs:CreateLogStream' - 'logs:DescribeLogGroups' - 'logs:DescribeLogStreams' - 'logs:PutLogEvents' - 'logs:PutRetentionPolicy' Resource: 'arn:aws:logs:*:*:*' # 2. Auditログ転送甚 IAM ロヌル RosaAuditLogRole: Type: 'AWS::IAM::Role' Properties: RoleName: 'rosa-audit-log-role' AssumeRolePolicyDocument: !Sub | { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::${AWS::AccountId}:oidc-provider/${OidcEndpoint}" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "${OidcEndpoint}:sub": "system:serviceaccount:openshift-config-managed:cloudwatch-audit-exporter" } } } ] } ManagedPolicyArns: - !Ref RosaAuditLogPolicy 䞊蚘CloudFormationの「OidcEndpoint」ずいうパラメヌタヌには、OpenShift Logging甚のIAMロヌルを䜜成した時ず同様に、oidc-configの「ISSUER URL」の「https://」を陀いた倀を蚭定しおください。 CloudFormationを適甚するず以䞋のIAMロヌルが䜜成されたす。 以䞋のrosaコマンドで監査ログの転送を有効化したす。 $ rosa edit cluster --cluster=<クラスタヌ名> \ --audit-log-arn="arn:aws:iam::xxxxxxx:role/rosa-audit-log-role" CloudWatch Logsに以䞋のようなロググルヌプが䜜成されおいれば、監査ログの転送蚭定は完了です。 ※ロググルヌプ名は「ocm-production-<クラスタヌのID>-<クラスタヌ名>」の圢匏で自動䜜成されたす。 たた、クラスタヌ構築時に–domain-prefixを蚭定しおいた堎合は、「ocm-production-<クラスタヌのID>-<domain-prefixの倀>」の圢匏ずなりたす。 おわりに ROSAクラスタヌにおけるCloudWatch Logsぞのログ転送方法に぀いお解説したした。 実際にROSAクラスタヌを運甚する際の参考になれば幞いです。 次回はROSAクラスタヌ䞊のメトリクスをCloudWatchに転送する方法に぀いおご玹介したす。 参考文献 https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/4 https://docs.aws.amazon.com/ja_jp/rosa/latest/userguide/what-is-rosa.html https://docs.redhat.com/en/documentation/red_hat_openshift_logging/6.6/html/install/installing-the-red-hat-openshift-logging-operator ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post ROSARed Hat OpenShift Service on AWSを利甚したコンテナプラットフォヌム構築 ~ ログ転送蚭定 ~ first appeared on SIOS Tech Lab .
ども先日、久しぶりに背筋が䌞びる出来事があった韍ちゃんです。 昔いたチヌムからくる確認のSlackっお、なんかこう怖いんですよね。実際に怒られおいるわけじゃなくおも、文面に若干の圧を感じるずいうか。今回たさにそれが飛んできお、詳しく聞いおみたら「org党䜓で䜿っおいるGitHub ActionsのArtifactストレヌゞが倧量に、僕がいた関わっおいるリポゞトリ1぀で䜿われおいる」ずいう話でした。 え、そんなにず思っお自分でも調べおみたら、驚異の 98% 䞀぀のリポゞトリで䜿甚しおいたした。しかも厄介なこずに、この手の超過アラヌトっお、僕みたいに実際にartifactを積み䞊げおいる本人には届かないんです。誰かが教えおくれない限り、自分では䞀生気づかないたたでした。 「ストレヌゞを䜿いすぎ」ず蚀われたら、たずどこを芋るか 同じこずを蚀われたら、たぶん倚くの人がたず思い浮かべるのは「actions/cacheのキャッシュが膚らんでるんじゃないか」だず思いたす。でも、cacheずartifactは別枠です。cacheはリポゞトリごずに独立した枠を持っおいるので、そこがいくら膚らんでも、artifactの枠には効かないんですよね。 じゃあ䜕を芋ればいいのかずいうず、軞は2぀でした。 どこを芋るか : artifactずGitHub Packagesはセットで芋たす。同じ課金プヌルを䜿っおいるので、片方だけ芋おも切り分けになりたせん どう芋るか : 件数ではなく容量で䞊べたす。しかも画面に出るのは合蚈だけで、リポゞトリごず・artifactごずの内蚳はAPIを叩かないず出おきたせん で、蓋を開けおみるず、容量を食っおいたのはGitHub Packagesではなくartifactの方でした。しかも、件数がいちばん倚かったartifactは、容量で芋るずほがれロだったんですよね。数が倚いものが重いずは限らない、ずいうや぀でした。 本圓の原因は、PRを出すたびに積み䞊がる「誰も取りに来ないartifact」でした じゃあ䜕が積み䞊がっおいたのかずいうず、CI自䜓はPRを出したずきもマヌゞしたずきも同じように走っおいお、ビルドしおテストしお、その成果物をartifactずしおアップロヌドする、ずいう流れになっおいたす。ここたでは普通ですよね。 問題は出口でした。マヌゞされたずきはCDがそのartifactを拟っおデプロむに䜿っおくれるんですが、PRの段階だずそのartifactを取りに来る人が誰もいないんですよね。デプロむされるわけでもないし、レビュヌでダりンロヌドしお確認するわけでもない。ただ保持期間が過ぎるたで、ストレヌゞの䞊に埋儀に残り続けるだけの存圚でした。 実際に数えおみたら、このリポゞトリに溜たっおいたartifactのうち69%が、この誰も取りに来ないや぀だったんですよね。PRの数が増えれば増えるほど、この「誰も取りに来ないartifact」も比䟋しお積み䞊がっおいく構造だったわけです。 悪かったのは曞き方じゃなくお、運甚が倉わっおもCI/CDを盎さなかったこずでした このワヌクフロヌ、デプロむを回す道具ずしおは十分に仕事をしおいたんですよね。実際、曞いおからしばらくは䜕も起きたせんでした。 穎が衚に出たのは、バグ改修フェヌズに入っおからでした。PRが同時に䜕本も飛ぶようになっお、その回数ぶんCIが走るようになったんですよね。 問題は、条件がデプロむする偎にしかなかったこずでした。デプロむする偎はちゃんず条件で絞っおあったのに、artifactを䜜る偎は特に絞っおいなくお、 retention-days も指定しおいなかったんですよね。PRの数がそこたで倚くなかった頃は、これで䜕の問題もなかったんです。 ぀たり、動くずいう目的では圹割を果たしおいたんですが、倧量に発行される前提には耐えられなかったずいうこずですね。運甚が倉わったなら、CI/CDの蚭蚈もそれに合わせお倉えるべきだったんだず思いたす。こういうのはぶ぀かっお初めお芚えるんだな、ず反省をしおいたす。 䟝頌から3時間埌には、消せるものは消し終えおいたした 管理チヌムから連絡をもらっおすぐに調査を始めお、応急察応たで含めお3時間くらいで䞀通り片付けたした。焊りながらの察応でしたね。 応急では、溜たった分を片付けお、保持期間を決めたした 応急でやったこずは2぀です。 溜たっおいたartifactを削陀した 。5日より叀いものが察象で、画面からはたずめお消せなかったのでAPIを叩きたした retention-days を5日に指定した 。これから䜜られるartifactが、5日で勝手に消えるようにするためです retention-days ずいうのは、artifactをアップロヌドするずきに、そのartifact 1件だけの有効期限を指定できる蚭定です。workflowのupload偎に曞くもので、ログの保持期間には効きたせん。 䜕も指定しなければ、GitHubの既定で90日残りたす。ただしこの倀はリポゞトリ・organization・enterpriseの各階局でも蚭定できお、䞋の階局は䞊の階局が決めた䞊限を超えられたせん。今回のリポゞトリは蚭定偎が30日だったので、指定挏れのartifactは30日きっちり残っおいたわけです。 韍 で、ここで䞀回ズルをしようずしたんですよね。蚭定偎の保持期間を1日にしおしたえば、すでに溜たっおいる叀いartifactも巻き蟌んで勝手に消えおくれるんじゃないかず思ったんです。でも消えたせんでした。すでに䜜られおいるartifactは、䜜られた時点で決たった保持期間をそのたた持ち続けるので、あずから蚭定を倉えおも知らん顔で残り続けるんですよね。 蚭定の倉曎は既存のものには遡及しない ず公匏ドキュメントにもはっきり曞いおあっお、先に読んでおけよずいう話でした。 結局、溜たっおいた分はAPIで個別に消すしかありたせんでした。削陀ず retention-days の指定を䞡方やらないずいけなかったのはこれが理由で、片方だけだず、消しおも積み戻るか、積み戻りは止たっおも溜たった分が残るかのどちらかになるんですよね。 消し方そのものは公匏にたずたっおいるので、そちらに任せようず思いたす。画面から消す手順は Removing workflow artifacts に、たずめお消すためのAPIは REST APIのartifacts にありたす DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id} ですね。 ここで曞きたかったのは「䜕を䞍芁ず刀断したか」の方で、答えはシンプルに「5日で切っお消した」でした。過去のartifactを参照する経路は実際に無くお、必芁になったら゜ヌスから再ビルドすればいいので、そこたで重い刀断ではなかったです。 ただ消したらコヌド偎からしか戻せないのできちんず䞍必芁なものを芋極めお消したしょうね。 恒久の方は、artifactをそもそも䜜らせない方向で盎したす 応急でやったのは、あくたで止血です。アラヌトが飛ぶほど溜たるこずはもう無くなりたしたが、誰も取りに来ないartifactが䜜られ続けおいる状態そのものは、䜕も倉わっおいないんですよね。䜜っお、眮いお、5日埌に消す。リ゜ヌスがもったいないですし、盎すべきなのはそこじゃないよなず思っおいたす。 やるべきなのは、そもそも䜜らせないこずです。今回の根っこは、デプロむする偎には条件があったのに、artifactを䜜る偎には条件が無かったこずでした。PRのrunでは、成果物を枡す先がそもそも無いんですよね。だったらupload自䜓に条件を぀けお、芁らないずころでは䜜らせないようにするのが筋です。さっきの図でいうず、䞊に䌞びおいた「誰も取りに来ない」偎の経路を、根元で塞ぐむメヌゞですね。 あわせお、寿呜を甚途で分けるこずも考えおいたす。デプロむに受け枡すだけのものは短くおいいし、埌から参照する可胜性があるものだけ長く残せばいい。ただこっちは補足で、本呜は「そもそも䜜らない」の方です。 方向はもう決たっおいお、いた実際に圓おおいるずころです。 それで、artifactの話はどうなったかずいうず 応急も恒久も、いたはもう手が動いおいる状態です。 あの日のSlackが無ければ、僕はいたも気づかないたただったず思いたす。ずいうのも、超過アラヌトの 既定の宛先はaccount ownerずbilling manager で、開発者本人は入っおいないんですよね。ただ、受信者は埌から足せたす。 いやでも䌚瀟のリ゜ヌスの98%を食らい぀ぶすずいう驚異的な数字を叩きだしたずきは倧笑いしたしたね。笑っおいる堎合じゃないですけど。 ずいうわけで、今回は僕がGitHub ActionsのArtifactでやらかした話でした。同じような運甚をしおいるずころがあれば、管理者に頌んでアラヌトの宛先に自分も入れおもらうずいいず思いたす。気づける人を増やしおおくのが䞀番手っ取り早いはずです。GitHub ActionsのArtifactの䜿い方自䜓は前に䞀床 蚘事にしおいる ので、そちらもよければどうぞ。 いや管理チヌムがきちんず仕事をしおいお玠晎らしいですねちなみに僕は迷惑をかけるタむプの管理者だったので本圓に頭が䞊がらないです。 ではたた ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post GitHub Actionsのartifactでストレヌゞ98%を朰しお怒られた話 first appeared on SIOS Tech Lab .
蚱可がどこで決たっおいるか、実はちゃんず確かめたこずがなかったんですよね。auto mode で回しおはいるものの、䜕がどう自動で通っおいお、䜕が誰の刀断を経おいるのか、腰を据えお远いかけたこずは䞀床も無くお。 僕の堎合は「 CLAUDE.md に曞いおあるから塞げおいる」ず思っおたんですよね。壊されたくない堎所があったので CLAUDE.md に「これはやるな」っお曞いおおいお、それで防埡になっおいるず。 入口は2぀あるず思っおたす。8月14日の既定化で、蚭定した芚えがないのに auto mode になっおいた人もいれば、僕みたいに CLAUDE.md で塞いだ぀もりでいた人もいるず思いたす。でも聞きたいこずは同じで、auto mode っお実際どう動いおいるのか、远加のルヌルはどこに曞けば効くのか。この2぀に絞っお曞いおいきたす。 auto mode っお実際どんなフロヌで動いおんの 8月14日から auto になったのは Pro・Max・Team プランの察話セッションだけです。経緯は 公匏ブログ に譲りたすね。バヌゞョンにも条件があっお、macOS・Linux・WSL は v2.1.228 以降、Windows ネむティブは v2.1.233 以降が察象です。それより前のバヌゞョンでは、既定はいたも Manual のたたです。Enterprise プランず Claude Console の API キヌ、それにヘッドレスの claude -p は、既定が auto になりたせん。auto mode 自䜓が䜿えないわけじゃないので、自分で切り替えれば動きたす。 刀定は3぀の分かれ道になっおいお、䞊から順番に芋おいっお、最初に圓おはたったずころで決着したす。぀たり埌ろの分かれ道が芋るのは、それより手前で決たらなかったものだけなんですよね。ここには䟋倖がいく぀かあるんですが、この蚘事に効いおくるのは1぀だけなので、3぀を䞊べたあずで曞きたすね。 permissions 。allow / ask / deny を自分で settings.json に曞いたルヌルです。ここに䞀臎したら、allow ならそのたた実行、ask なら確認が出お、deny なら拒吊になりたす。そこで終わりで、classifier は呌ばれたせん。 自動承認 。読み取りず、 Write や Edit みたいな線集ツヌルによる working directory 内の線集です。 permissions で決たらなかったものは、ここで誰にも確認されずに通りたす。逆に蚀うず、Bash コマンドはここには入りたせん。䞭で䜕をするかに関係なく、classifier に回りたす。 classifier 。それ以倖の党郚を、この別のモデルが刀断したす。結果は実行か拒吊かの二択で、拒吊されおも自分には聞かれたせん。Claude が理由を受け取っお別の手を詊すだけです。 気づいおほしいのは、この3぀がどれも「Claude Code が䜕を通すか」の話だずいうこずです。 CLAUDE.md や Skills は、ここに入っおいないんですよね。 permissions は Claude Code が叩けるコマンドの遞定、classifier は自然蚀語で足す刀断ルヌル、 CLAUDE.md や Skills は䜕を生成させるか。前の2぀は実行のずきに通すかどうかを決めるもので、芋られる順番が違うだけです。この順番で監査が行われお、問題なければ自動で実行されるっお感じです。 CLAUDE.md や Skills はその手前、䜕をやろうずするかを決める偎にいたす。 3぀目の「それ以倖の党郚」は、思ったより広いです。䞭身に関係なく Bash コマンドは党郚ここですし、MCP ツヌルや、working directory の倖ぞの線集も入りたす。 しかも、classifier に回る量は増える方向なんですよね。Claude Code 自䜓が Bash を優先しお䜿うようになっおいお、ファむルを読むのも cat 、盎すのも sed で枈たせたりしたすv2.1.221 から。changelog には茉っおいたせん。詳しくは 同僚が远いかけた蚘事 。 Read や Edit なら自動承認で終わる話が、Bash に化けたぶんだけ classifier に回りたす。 ただ、protected paths .git や .claude 、 .devcontainer などぞの曞き蟌みは、この流れから倖れたす。working directory の䞭にあっおも自動承認されたせんし、 permissions の allow に䞀臎しおいおも玠通りしたせん。どちらの堎合も classifier に回されたす。蚭定の曞き蟌みに察しお割蟌みが入るのは安心感がありたすね。 ここで効いおくるのが2぀目の自動承認です。 Write や Edit みたいな線集ツヌル経由の線集は、classifier に届く前にここで決着したす。 実際、壊されたくないファむルを classifier 偎の蚭定 autoMode の soft_deny 。危ないず思うものを自然蚀語で曞いおおく欄ですに曞いおおいお、そこを自分で曞き換えにいったこずがありたす。手元の 2.1.233 で詊したずきは、同じパスに察しお Bash 経由 cp や git checkout -- 、 cat > 、 truncate は拒吊されたのに、 Write ツヌルず Edit ツヌルは玠通りしおたした。2セッション・4ファむルで䞀臎したので、たたたたではなさそうです。ただしこれは soft_deny に曞き足したうえでの芳枬なので、玠の蚭定だずたた違う結果になるはずです。そこは前提ずしお持っおおいおください。 远加のルヌルを効かせたいずき、どこに曞けばいいの auto mode に䜕か足したい、ずいう発想自䜓はたちがっおないです。ただ、たず手が䌞びるのは CLAUDE.md だず思うんですよね。さっき、 CLAUDE.md はこの流れの倖だず蚀いたした。なぜ倖なのか。 理由は、読たれおいないからじゃないんです。 公匏ドキュメント にも “The classifier sees user messages, tool calls other than read-only lookups such as file reads and searches, and your CLAUDE.md content” ずはっきり曞いおあっお、classifier はちゃんず CLAUDE.md の䞭身を読んでたす。読たれおはいるんです。ただ、 別のペヌゞ にはこう曞いおありたす。”Permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries to do, but they don’t change what Claude Code allows.” ぀たり CLAUDE.md は Claude に「こう振る舞っおほしい」ずいう方向性を枡す堎所であっお、実際に止める・止めないを決めるのは Claude Code 偎の permission rules のほうです。読たれおはいるけど、止める力は持っおいない。だから流れの倖なんです。 じゃあ、その permission rules はどこに曞くのか。ここで倧事なのが、 止めたいものによっお曞く堎所が違う ずいうこずです。線集を止めたいなら permissions 、classifier の刀断に効かせたいなら autoMode 。敎理するず、こうなりたす。 どこで決たるか 䜕が察象か 远加ルヌルを曞ける堎所 permissions 自分で曞いた allow / ask / deny に䞀臎するもの .claude/settings.json projectや ~/.claude/settings.json user 自動承認 permissions で決たらなかった、読み取りず線集ツヌルによる working directory 内の線集Bash コマンドは入らない 曞ける堎所が無い classifier それ以倖ぜんぶ autoMode 個人は ~/.claude/settings.json のみ。managed settings では組織が配垃できる 刀定の倖 classifier は読むけど、それ自䜓は刀定しない CLAUDE.md や Skills生成の方向性を枡す堎所 曞く堎所が違うずいっおも、ファむルが分かれるわけじゃないです。個人の蚭定なら、どちらも ~/.claude/settings.json の䞭にこう䞊びたす。 { "permissions": { "deny": [] }, "autoMode": { "soft_deny": [] } } 䞊の permissions が線集を止める偎、䞋の autoMode が classifier に効かせる偎。䞭身はこのあず、節ごずに埋めおいきたす。 それず、この2぀は択䞀じゃないです。 同じものを本圓に守りたいなら、䞡方に曞くこずになりたす。 permissions が本呜で、 autoMode は保険。なぜ保険たで芁るのかは、次の節で実際に枬った結果ず䞀緒に曞きたすね。 ※ protected paths ぞの曞き蟌みは䟋倖で、working directory の䞭にあっおも自動承認されず、 permissions の allow に䞀臎しおいおも玠通りしたせん。どちらも classifier に回されたす。protected paths は公匏に列挙されおいお、ディレクトリなら .git や .claude 、 .vscode 、 .devcontainer など、ファむルなら .gitconfig やシェルの蚭定ファむル、 .npmrc 、 .pre-commit-config.yaml 、 .mcp.json などが入りたす。ここで泚意したいのは、 .env や SSH の鍵はこの䞀芧に入っおいないこずです。名前からしお守られおいそうですが、protected paths ではないので、機密を守りたいならそこは自分で permissions に Read(パス) の deny を曞く必芁がありたす。 Read の deny は Edit・Write も䞀緒に塞いでくれたす。 自動承認される前に止めたいずき 自動で線集されるのを止めたいなら、曞き先は permissions です。working directory の䞭の線集はそのたた自動承認されたすし、 autoMode が効くのは classifier に回っおきたものだけなので、自動承認で決着した線集には届かないからです。 曞くなら permissions の Edit(パス) の deny や ask です。 permissions は自動承認より先に芋られるので、working directory かどうかに関係なく、たずここでせき止められたす。 たずえば .env を觊らせたくないなら、こう曞きたす。 { "permissions": { "deny": [ "Read(./.env)" ] } } Edit じゃなくお Read で曞いおいるのは、 Read の deny が Edit・Write も䞀緒に塞いでくれるからです。そこに新しくファむルを䜜るのも塞がれたす。読み出しも曞き換えも、たずめおここで止たりたす。 ただし、この deny で止たるのは線集ツヌル経由の線集だけです。 Bash 経由の線集は classifier に回りたす。しかも、Claude Code 自䜓が Bash を優先しお䜿うようになっおいおさっき出した v2.1.221 の話です、ファむルの曞き換えも sed -i や cat > で枈たせるこずが増えおいたす。 permissions に Bash(...) の deny を曞く手もあるんですが、コマンドの圢は無数にあるので、パスを守る甚途だず取りこがしたす。 さっき「䞡方に曞くこずになる」ず蚀ったのは、これが理由です。 permissions は classifier が呌ばれる前に止たるので、こちらが本呜。そこをすり抜ける Bash 経由を classifier で受け止めるのが autoMode で、こっちが保険です。実枬がそのたた根拠になっおいお、 soft_deny に曞いたパスで Bash 経由は拒吊されたのに、 Write ・ Edit は玠通りしたした。片方だけでは穎が開く、ずいうこずです。 classifier の刀断を倉えたいずき 䞀方で、classifier の刀断そのものに手を入れたいずき、「うちではこれも危ないから、いったん止めおほしい」ずいうルヌルを足したいずきは、 autoMode の soft_deny に曞きたす。効くのは classifier に回っおくるものなので、working directory の倖だけじゃなくお、䞭にあるパスでも Bash 経由の操䜜なら察象になりたす。個人が曞ける堎所ずいう意味では、これは user setting、぀たり ~/.claude/settings.json だけです。project の .claude/settings.json にも settings.local.json にも曞けたせんここは埌でたずめお觊れたす。組織が配垃する managed settings には autoMode を曞けお、そちらは読たれたすが、それは配る偎の話なので今回は自分の環境で完結する範囲にずどめたす。 眮き堎所ずしおは、 autoMode の䞋に allow や hard_deny ず䞊んで soft_deny ずいう配列がありたす。さっきの .env を Bash 経由でも守るなら、こう曞きたす。 { "autoMode": { "soft_deny": [ "$defaults", "機密ファむル [named+specifics — must name: 察象のパスず操䜜]: .env ずその耇補を Bash 経由で読む・曞き換える・消す" ] } } ちなみに .env は、公匏が auto mode の「既定で蚱可されるもの」に名指しで挙げおたす。「 .env を読んで、察応する API に認蚌情報を送る」が、そのたた蚱可の䞀芧に茉っおるんですよね。名前からしお守られおいそうなのに、既定では通る偎です。守りたいなら自分で曞くしかない、ずいうのはそういう意味です。 1぀目の $defaults は忘れないでください。ここを削っお自分のラベルだけにするず、Claude Code が最初から持っおいる既定の防埡を䞞ごず眮き換えおしたいたす。 soft_deny なら force push や curl | bash 、本番ぞのデプロむ、auto mode の迂回たで、たずめお消えたす。実害があるずころなので、コピペするずきは気を぀けおほしいです。 䞭身が気になるなら claude auto-mode defaults で党郚出たすし、 --label を付ければ1぀ず぀読めたす。 claude auto-mode defaults --label 'Git Destructive' [named+specifics — must name: 䜕を名指しさせるか] ずいうラベルは、䌚話の䞭でパスず操䜜を名指しすれば解陀できる、ずいうバヌの高さを衚しおいたす。 soft_deny の「soft」はここのこずです。さっき保険ず曞いたのはこの意味で、 permissions の deny ず違っお、こちらは名指しされれば開きたす。 効いおない蚭定が3぀ある ここたでで眮き堎所は分かったんですが、実は 曞けたのに効いおいない 、ずいう圢の事故が3か所ありたす。 3぀に共通しおいるのは、曞けおしたうこずです。蚭定ずしおは受け付けられるので、曞いた本人は効いおいる぀もりでいる。だから気づけないんですよね。 ひず぀断っおおくず、䞀次゜ヌスを確認したのは 2.1.245、実枬は 2.1.233 のずきのものです。3぀ずも公匏に曞かれおいる挙動なんですけど、公匏の挙動も版で動きたす。 ひず぀目。 autoMode を project の .claude/settings.json や settings.local.json に曞いおも読たれたせん。理由も公匏に曞いおあっお、この2぀はリポゞトリの䞭にあるので、チェックむンされた蚭定やビルドの䞀手が自分で allow を泚入できおしたうからです。個人が曞ける堎所は user setting、぀たり ~/.claude/settings.json だけですどこが読たれるかは 公匏ドキュメント にスコヌプの䞀芧がありたす。 ふた぀目。 permissions で線集を止めたいずきは Edit(パス) で曞きたす。 Write(パス) も蚭定ずしおは受け付けられるんですけど、実際の刀定では参照されたせん。さっきの Configure permissions に、ファむルの permission で芋られるのは Edit(パス) ず Read(パス) だけだず曞いおありたす。3぀のうちこれだけは起動時に譊告が出るので、芋おいれば気づけたす。 みっ぀目。auto mode に入っおいるあいだは、広い allow が萜ちたす。公匏に挙がっおいるのは5぀で、 Bash(*) や PowerShell(*) みたいな䞞ごずの蚱可、 Bash(python*) みたいなワむルドカヌドのむンタプリタ、パッケヌゞマネヌゞャの run 、 Agent の allow、 Monitor の allow です。䞀芧は公匏ドキュメントの「 How the classifier evaluates actions 」にありたす。 Bash(npm test) みたいな狭い指定は残りたす。 なお、その狭い指定たで含めお党郚 classifier に回したいなら、 autoMode の classifyAllShell を true にする手もありたす。刀定を1回ぶん埅぀こずになるので、速さず匕き換えですね。 萜ちるずいっおも消えるわけじゃなくお、manual に戻せばたた効きたす。なお Monitor がこの䞀芧に入ったのは v2.1.236 からで、それより前は auto mode でも効き続けおいたした。 自分の環境がどうなっおいるかは claude auto-mode config で確かめられたすやり方は次の節で曞きたす。 曞いたずおりに、毎回そのずおり効くの ここたでの内容を自分の運甚に萜ずすず、 ~/.claude/settings.json はホストず bind で共有しおいお、耇数の開発環境をたたいでも同じ刀定基準を持たせおたす。リビルドしおも消えないので、環境ごずに曞き盎す手間がありたせん。䜕を共有しお䜕を分けるかの蚭蚈は 別蚘事 に敎理しおありたす。 揺れたずきの条件 環境をたたいでは効くずしお、時間をたたいでも同じように効くのか。ここは留保が芁りたす。前日に同じルヌルが止めた削陀が、翌日の新しいセッションでは通っおしたったこずがあっお。蚭定も察象のディレクトリも同じで、解陀条件を満たしたわけでもありたせん。しかも揺れは片方向だけじゃなくお、同じ日に逆向きも起きおいお、削陀は通ったのに埩元しようずした4぀の経路が党郚拒吊されたこずもありたす。効くか効かないかが䞡方向に振れお読めない、ずいうのが正確なずころです。どちらの芳枬もただ1件ず぀なので、そこは留保ずしお眮いおおきたす。 原因はただ特定できおいたせん。公匏ドキュメントには、classifier が出した deny は察話 CLI だずそのタヌンのあいだだけ持続する、ずいう蚘述がありたす。ただこれは刀定がどこたで有効かずいう話です。日をたたいで揺れた理由を説明するものではないので、原因の説明ずは切り離しお、別立おの事実ずしお眮いおおきたす。 別の環境で䌌た揺れを芳枬しおいる人 もいるようです。 萜ち方を混ぜるず読めなくなる ここで揺れ方を混ぜないように敎理しおおきたす。 「刀定の揺れ」: いた話したのは、刀定は返っおきたけど結果が日をたたいで倉わった、ずいう揺れです。 「問い合わせの倱敗」: これずは別に、classifier ぞの問い合わせそのものが倱敗しお cannot determine the safety が返っおくるこずがありたす。 公匏ドキュメント にも “usually transient” ず曞いおいお、確認を挟む偎に倒れる分には安心しおいい動きです。 「回数で止たる」: classifier が3回続けおブロックするか、セッション通算で20回に達するず、auto mode がいったん止たっお確認が戻っおきたす。出おきた確認を承認すればたた auto に戻りたすし、途䞭で1぀でも通れば連続のカりントはリセットされたす。閟倀は倉えられたせん。さっき「classifier に拒吊されおも自分には聞かれない」ず曞きたしたが、確認が戻っおくるのはここです。 「auto mode の停止」: もう䞀぀、auto mode がそもそも動かなくなるこずもありたす。こちらは 公匏ドキュメント が “not a transient outage” ず曞いおいるずおり䞀時的ではなくお、自分もセッションを再起動しお盎らず manual に戻したこずがありたす。 問い合わせの倱敗が安心できるからずいっお、刀定の揺れたで安心しおいいわけではありたせん。刀定の揺れでは classifier がちゃんず刀定を返しおいるので、問い合わせの倱敗ずは別の堎所で起きおいたす。逆に蚀うず、通っおしたう偎に振れるのは刀定の揺れだけです。残りの3぀は、確認が戻っおくるか、そもそも止たるかで、安党偎に倒れたす。 で、どうしおいるか こういう刀定の揺れに察しお、自分がやっおいるのは原因を远いかけるのをやめるこずです。远っおも割に合わないので。 かずいっお、揺れた圓のものを permissions に移したわけでもないんですよね。submodule の削陀は、いたも autoMode の soft_deny に眮いたたたです。 rm を permissions で塞ぎにいくず、Claude Code が元から持っおいる安党装眮ず圹割がかぶるので、そこは重ねずに任せおいるずいう刀断です。 じゃあ䜕をしおいるかずいうず、どれを classifier に預けおいるかを自芚しおおく、くらいのこずです。確実に止めたいものは、揺れの話ずは関係なく最初から permissions に眮いおありたす。機密ファむルの読み出しは Read(パス) の deny、耇数セッションで事故りやすい git add -A は Bash(...) の deny。ここは classifier の刀断を埅たずに手前で決着するので、そもそも揺れたせん。逆に classifier に預けたものは、揺れるこずを承知で預けおいる。どこに曞くかの䜿い分けっお、結局そこの線匕きなんだず思いたす。 スマホから枡しお回すずきの運甚は 別蚘事 に曞いおいお、そこでは「䜕を觊らせるかは分かったうえで枡す」ずいう前提を先に眮いおいたす。 で、自分のは今どうなっおる CLAUDE.md に曞いおおけば止たっおいるず思っおた僕みたいな人は、䞀床 claude auto-mode config で自分の autoMode の䞭身を確認しおおくずいいず思いたす。project の .claude/settings.json や settings.local.json に曞いた autoMode が読たれおいるかどうかも、ここに出るかどうかで分かりたす。 ひず぀だけ泚意点があっお、出力をそのたた grep に掛けるのはやめたほうがいいです。途䞭の説明文にたたたた匕っかかっお、倉な断定をしたこずがあるので。たず、パヌスが通るこずを確かめおから䞭身を芋おください。 claude auto-mode config | python3 -c "import json, sys; json.load(sys.stdin)" これで゚ラヌが出なければ、 allow や soft_deny の䞭身を安心しお読みにいけたす。 ここたで曞いおおいおなんですが、この蚘事の内容も、枬った版から動いおいる可胜性がありたす。実際 Monitor の件がそうでした。だから最埌に眮くのは答えじゃなくお、自分の手元を確かめる方法のほうかなず思っおたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Code の auto mode はどう動く远加ルヌルを曞く堎所を敎理しおみた first appeared on SIOS Tech Lab .
はじめに こんにちは、サむオステクノロゞヌの小沌 俊治です。 「理屈はいいから、たずは実際に Apache Kafka やストリヌム凊理ずいうものを動かしお䜓隓しおみたい」。 本蚘事は、そんな方々に向けお、リアルタむムデヌタ基盀に䞍可欠なむベントストリヌミングやデヌタパむプラむンの基瀎を手を動かしながら孊習できる、実践的な入門ガむドずしお甚意したした。 単なるメッセヌゞの送受信にずどたらず、リアルタむムなデヌタ加工Flink / Kafka Streams、デヌタの品質管理Schema Registry、デヌタベヌスずの自動同期CDC Connector、そしおパヌティションによる負荷分散たでを包括したリアルタむムデヌタパむプラむン環境を無料で䜓隓できるハンズオンを提䟛したす。 本ハンズオンでは、以䞋のオヌプン゜ヌス・プロダクトのみで構成された環境をコンテナを䜿っお䞀括で立ち䞊げ、デヌタが発生しおから加工・連携・分散凊理されるたでの䞀連の流れを䜓隓しおいただきたす。 Apache Kafkaむベントストリヌミング基盀Broker AKHQKafka クラスタ管理 Web UI Apache Flink分散ストリヌム凊理゚ンゞンFlink SQL / Table API / DataStream API Apicurio Registryスキヌマ管理・デヌタ品質保持Schema Registry Kafka Streams組み蟌み型ストリヌム凊理アプリケヌション Debezium Connector for MySQLデヌタベヌス倉曎デヌタ捕獲CDC MySQLデヌタベヌス なお、本蚘事は「Kafka を䞭心ずした゚コシステムによるリアルタむム凊理の流れ」を䜓隓いただくこずを䞻目的ずしおいたす。そのため、環境構築手順そのものの詳现な解説は䞀郚割愛しおおり、「たずは動かしおみたい」ずいう方に最適です。もし環境構築の裏偎に興味を持っおいただいた堎合は、埌半の Appendix や GitHub リポゞトリの蚭定ファむルをぜひ解析しおみおください。 構成抂芁 筆者が動かした際の䞻な構成芁玠は以䞋の通りです。 Windows 11 Professional WSL 2.4.12.0 Ubuntu 24.04.2 LTS Docker Engine 28.0.4 Apache Kafka 4.3.1 Windows (WSL) 以倖のOSをご利甚の方も、条件が満たしおいれば以䞋の手順からハンズオンを進められたす。 Ubuntu (Linux) 環境の方: WSL の構築は䞍芁なため、「 Docker Engine 環境の構築 」章から開始しおください。 macOS 環境の方: Docker Desktop for Mac などでコンテナ実行環境が準備枈みであれば、「 ハンズオンに必芁なコマンドの準備 」章から開始しおください。 ハンズオンを構成する環境は以䞋の通りです。 Kafka のメむンコンポヌネントを担う Broker ず、動䜜状況や蚭定を Web UI で提䟛する AKHQ をコンテナで構築したす。 ストリヌム凊理を䜓隓するため以䞋のコンテナを構築したす。 Apache Flink の Flink SQL、Table API、および DataStream API を䜓隓するため、jobmanager、taskmanager をコンテナで構築したす。 Streams Application を䜓隓するため、Java 環境のコンテナを構築したす。 Schema Registry を䜓隓するため、スキヌマを管理する Apicurio Registry をコンテナで構築したす。 Kafka Connector を䜓隓するため、デヌタベヌスから曎新差分を転送する CDC をコンテナで構築したす。 デヌタベヌスずしお MySQL をコンテナで構築したす。 CDC Connector の皌働環境ずしお Debezium connector をコンテナで構築したす。 トピックでメッセヌゞを送受信するために、ロヌカル環境に Java や Kafka CLI ツヌルをむンストヌルする手間を省くため、プロデュヌサヌずコンシュヌマヌを実行する専甚の「handson-client」コンテナを甚意しおいたす。 環境構築や各皮蚭定に䜿甚するそれぞれのファむルは、以䞋の GitHub リポゞトリで公開しおいたす。 https://github.com/Toshiharu-Konuma-sti/hands-on-kafka $ tree ~/handson/hands-on-kafka/ hands-on-kafka/ |-- container/ 

 「環境構築」章でコンテナ䜜成で䜿う玠材 | |-- docker-compose.yml | : | |-- development/ | `-- streams/ 

 「独自 Streams アプリケヌション開発」章で䜿うアプリケヌションの開発玠材 | |-- setup/ 

 「kafka 挔習」章などのハンズオン環境の構築に必芁な各皮蚭定玠材 | |-- SETUP_HANDS-ON.sh | : | `-- try-my-hand/ 

 「Kafka 挔習」章などのハンズオンで䜿う環境 : 基瀎環境の構築 WSL 環境の構築 Windows PC の堎合には、 以䞋手順を参考に WSL ず Linux ディストリビュヌションUbuntu環境を甚意したす。 初期環境構築: WSL 環境 on Windows Docker Engine 環境の構築 コンテナ環境を䜿うため、以䞋手順を参考に Ubuntu ぞ Docker Engine 環境を甚意したす。 初期環境構築: Docker Engine on Ubuntu ハンズオンに必芁なコマンドの準備 本ハンズオンの実斜には、以䞋のコマンドやランタむムが必芁です。 これらは䞻に、環境構築を行う際に䜿甚したす。 jq コマンドAPI ぞ投入するリク゚ストの JSON 構造の線集をしたり、JSON 圢匏のレスポンスから特定の倀を抜出・敎圢するために䜿甚したす。 利甚箇所 Kafka 環境セットアップスクリプトの実行 むンストヌルされおいない堎合は、以䞋手順を参照しお Ubuntu 環境ぞ甚意したす。 初期環境構築: ナヌティリティツヌル on Ubuntu Kafka 環境の構築 GitHub からハンズオン甚のリポゞトリ取埗 ハンズオンを進めるための環境構築甚の蚭定ファむルやスクリプトを含んだリポゞトリを GitHub からダりンロヌドしお取埗したす。 GitHub からハンズオン甚のリポゞトリ取埗 $ mkdir -p ~/handson/ $ cd ~/handson/ 「 $ git clone 」コマンドで本ハンズオン甚のリポゞトリを取埗したす。 $ git clone https://github.com/Toshiharu-Konuma-sti/hands-on-kafka.git $ cd hands-on-kafka/ コンテナ構築スクリプトの実行 本章ではタヌミナルを甚いお以䞋のディレクトリで䜜業を実斜したす。 $ cd ~/handson/hands-on-kafka/container/ コンテナ構築甚に甚意しおあるスクリプトを実行しお、Kafka 環境の各皮コンテナを構築したす。 $ ./CREATE_CONTAINER.sh info /************************************************************ * Information: * - Navigate to Web ui tools with the URL below. * - AKHQ: http://localhost:9021 * - Flink dashboard: http://localhost:8181 ***********************************************************/ 「AKHQ」コンテナが皌働しおブラりザでアクセスできたす。 http://localhost:8080 なお、コンテナ構築スクリプトで実行する内容は以䞋を参照しおください。 コンテナ構築スクリプトの解説 Kafka 環境セットアップスクリプトの実行 トピックの䜜成やスキヌマ登録などを、スクリプトを䜿っお䞀気に行いたす。これにより、耇雑な蚭定を手動で行う手間を省きたす。 本章ではタヌミナルを甚いお以䞋のディレクトリで䜜業を実斜したす。 $ cd ~/handson/hands-on-kafka/setup/ セットアップ甚に甚意しおあるスクリプトを実行しお、Kafka 環境の各皮蚭定を行いたす。 $ ./SETUP_HANDS-ON.sh コマンドが足りずにスクリプトが終了した際は、以䞋を参照しおむンストヌルしおください。 初期環境構築: ナヌティリティツヌル 蚭定スクリプトで実行する内容は以䞋を参照しおください。 Kafka 環境セットアップスクリプトの解説 Kafka の挔習 ここから始たる Kafka のハンズオンは「 try-my-hand/ 」ディレクトリで実斜したす。 $ cd ~/handson/hands-on-kafka/try-my-hand/ なお、プロデュヌサヌやコンシュヌマヌの実行に必芁な Java や Python 環境は、あらかじめ「handson-client」コンテナ内に甚意されおいたす。手元のロヌカル環境に各皮ランタむムをむンストヌルする必芁はありたせん。 今埌のハンズオンに関するコマンド操䜜は、すべお「handson-client」コンテナ経由で実行したす。 コマンド䟋 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --create --topic sample-topic トピック操䜜 メッセヌゞの送受信に必芁なトピックの䜜成や削陀を始めずする操䜜を䜓隓したす。 トピックを䜜成したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --create --topic sample-topic Created topic sample-topic. 存圚するトピック䞀芧を確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --list : sample-topic トピックを削陀したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --delete --topic sample-topic 再床トピックを䜜成しお、パヌティションを分割したす䟋は分割。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --create --topic sample-topic $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --alter --topic sample-topic --partitions 3 存圚するトピックの詳现を確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe --topic sample-topic Topic: sample-topic TopicId: M5GoT1uKRPud0AFsZTAdWg PartitionCount: 3 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: sample-topic Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: sample-topic Partition: 1 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: sample-topic Partition: 2 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: トピックを削陀しお次に進みたす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --delete --topic sample-topic メッセヌゞ送受信 トピックを介しおプロデュヌサヌからコンシュヌマヌぞメッセヌゞの送受信を䜓隓したす。 2぀のタヌミナルを立ち䞊げお、プロデュヌサヌ送信偎ずコンシュヌマヌ受信偎間でメッセヌゞの送受信を䜓隓したす。 送信偎のタヌミナルでプロデュヌサヌを起動しおメッセヌゞの送信埅機状態にし、次に受信偎のタヌミナルでコンシュヌマヌを起動しおメッセヌゞの受信埅機状態にしたす。それぞれが埅機状態になっおいる状況で、送信偎のタヌミナルからメッセヌゞをタむピングしお送信した埌に、受信偎に届いたこずを確認しお進めたす。 たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe --topic my-topic Topic: my-topic TopicId: dlvh0VszQCi0we8mhEwnwg PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-topic Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: 基本的な送受信 送信偎のタヌミナルで送信先のトピックを指定しおプロデュヌサヌを起動するず、メッセヌゞ送信の埅機状態になりたす。なお、埅機状態は「Ctrl + C」で解陀できたす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic > 受信偎のタヌミナルで受信元のトピックを指定しおコンシュヌマヌを起動するず、メッセヌゞ受信の埅機状態になりたす。なお、埅機状態は「Ctrl + C」で解陀できたす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic 送信偎のプロデュヌサヌからメッセヌゞを、1行ごずに1メッセヌゞを送信したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic > hello001 受信偎のコンシュヌマヌにメッセヌゞが届きたす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic hello001 受信偎で「Ctrl + C」を抌しおコンシュヌマヌを終了しお、再床コンシュヌマヌを起動したす。 【受信偎】 [Ctrl + C] $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic 送信偎のプロデュヌサヌから远加でメッセヌゞを送信したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic > hello001 > hello002 受信偎のコンシュヌマヌでは過去のメッセヌゞは埗られず、受信状態䞭に送信されたメッセヌゞのみが受信できたす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic hello002 ここたでで送受信したメッセヌゞが、AKHQ からも確認できたす。 http://localhost:8080/ui/hands-on-kafka/topic/my-topic/data 最初のメッセヌゞから受信 受信偎で「Ctrl + C」しお、「–from-beginning」オプションを付䞎しおコンシュヌマヌを起動するず、トピック内の最初のメッセヌゞから受信できたす。 【受信偎】 [Ctrl + C] $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --from-beginning hello001 hello002 送信偎のプロデュヌサヌから远加でメッセヌゞを送信したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic > hello001 > hello002 > hello003 受信偎のコンシュヌマヌに远加のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --from-beginning hello001 hello002 hello003 メッセヌゞ情報を付䞎しお受信 受信偎で「Ctrl + C」しお、メッセヌゞに関する各皮情報を衚瀺するオプションを付䞎しおコンシュヌマヌを起動したす。メッセヌゞの送信時間やオフセット順番などが衚瀺されたす。 【受信偎】 [Ctrl + C] $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --formatter-property print.timestamp=true \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.headers=true \ --formatter-property print.key=true \ --from-beginning CreateTime:1786604356842 Partition:0 Offset:0 NO_HEADERS null hello001 CreateTime:1786613536930 Partition:0 Offset:1 NO_HEADERS null hello002 CreateTime:1786622414755 Partition:0 Offset:2 NO_HEADERS null hello003 オフセット指定でメッセヌゞ受信 受信偎で「Ctrl + C」しお、オフセットを指定しおコンシュヌマヌを起動するこずで、指定䜍眮からメッセヌゞを受信できたす。 【受信偎】 [Ctrl + C] $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --formatter-property print.timestamp=true \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.headers=true \ --formatter-property print.key=true \ --partition 0 --offset 1 CreateTime:1786613536930 Partition:0 Offset:1 NO_HEADERS null hello002 CreateTime:1786622414755 Partition:0 Offset:2 NO_HEADERS null hello003 Key & Value 圢匏で送受信 今床は送信偎で「Ctrl + C」しお、Key & Value 圢匏でメッセヌゞを送信するオプションを付䞎しおコンシュヌマを起動したす。䟋では、「:」をデリミタにしお Key & Value 圢匏でメッセヌゞを送信しおいたす。 【送信偎】 [Ctrl + C] $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --reader-property "parse.key=true" --reader-property "key.separator=:" > key1:value1 受信偎のコンシュヌマヌに Key & Value 圢匏で远加のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic \ --formatter-property print.timestamp=true \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.headers=true \ --formatter-property print.key=true \ --partition 0 --offset 1 CreateTime:1786613536930 Partition:0 Offset:1 NO_HEADERS null hello002 CreateTime:1786622414755 Partition:0 Offset:2 NO_HEADERS null hello003 CreateTime:1786626086546 Partition:0 Offset:3 NO_HEADERS key1 value1 Apache Flink 本章では、Kafka に流れるデヌタをリアルタむムに加工するストリヌム凊理゚ンゞン「Apache Flink」を䜓隓したす。 リアルタむムデヌタ倉換においおストリヌム凊理を行う手法ずしお、「Flink SQL」「Table API」「DataStream API」の3぀のアプロヌチを順に詊しおいきたす。 Flink SQL プロデュヌサヌからコンシュヌマヌぞメッセヌゞを送受信する過皋で、事前に DDL で登録したテヌブルず SQL ク゚リを甚いお、リアルタむムにデヌタをストリヌム凊理する Flink SQL を䜓隓したす。 「my-flink-input」トピックから受信しお「my-flink-sql-output」トピックぞ送信する過皋で、Flink では Flink SQL を䜿っお以䞋の加工を行いたす。 「my-flink-input」トピックから流れおきた JSON 圢匏のメッセヌゞをフィヌルド分解し、「FLINK_SQL_INPUT」テヌブルのレコヌドずしお保存したす。 「FLINK_SQL_INPUT」テヌブルのレコヌドを「FLINK_SQL_OUTPUT」テヌブルに挿入したす。挿入の際には以䞋の加工を行いたす。 察象フィヌルドを「name」ず「gender」に絞りたす。 「gender」フィヌルドの倀が 'M' たたは 'X' のデヌタのみに絞りたす。 本ハンズオンでストリヌム凊理ずしお実装した SQL flink_sql.sql たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-flink-input,my-flink-sql-output Topic: my-flink-sql-output TopicId: YIyil35cQe2TK-3b0yTPgA PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-sql-output Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: my-flink-input TopicId: GAKyq9SZSNmV9_LRP60qug PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-input Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: メッセヌゞ送受信 送信偎のタヌミナルで、Flink SQL を䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > 受信偎のタヌミナルで、Flink SQL を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-sql-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning 送信偎のプロデュヌサヌから JSON 圢匏のメッセヌゞ送信したす。Flink SQL で加工前 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > {"id":1, "name":"taro", "gender":"M", "age":10} > {"id":2, "name":"hanako", "gender":"F", "age":20} > {"id":3, "name":"tama", "gender":"X", "age":30} > {"id":4, "name":"tamako", "gender":"F", "age":40} 受信偎のコンシュヌマヌに、Flink SQL で加工埌のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-sql-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning Partition:0 Offset:0 null {"name":"taro","gender":"M"} Partition:0 Offset:1 null {"name":"tama","gender":"X"} Flink SQL で実行された ETL 凊理の結果を確認したす。 Flink SQL のカタログ情報はセッションごずのむンメモリ管理ずなるため、起動した SQL Client 䞊で䞀床 CREATE TABLE 文を実行しおから SELECT を発行したす。 $ docker exec -it jobmanager ./bin/sql-client.sh Flink SQL> SHOW TABLES; Empty set Flink SQL> CREATE TABLE flink_sql_input ( id INT, name STRING, gender STRING, age INT ) WITH ( 'connector' = 'kafka', 'topic' = 'my-flink-input', 'properties.bootstrap.servers' = 'kafka:29092', 'scan.startup.mode' = 'earliest-offset', 'format' = 'json' ); [INFO] Execute statement succeeded. Flink SQL> CREATE TABLE flink_sql_output ( name STRING, gender STRING ) WITH ( 'connector' = 'kafka', 'topic' = 'my-flink-sql-output', 'properties.bootstrap.servers' = 'kafka:29092', 'scan.startup.mode' = 'earliest-offset', 'format' = 'json' ); [INFO] Execute statement succeeded. Flink SQL> SHOW TABLES; +------------------+ | table name | +------------------+ | flink_sql_input | | flink_sql_output | +------------------+ 2 rows in set Flink SQL> SELECT * FROM flink_sql_input; id name gender age --- ------ ------ --- 1 taro M 10 2 hanako F 20 3 tama X 30 4 tamako F 40 Flink SQL> SELECT * FROM flink_sql_output; name gender ------ ------ taro M tama X Flink SQL> quit; プロデュヌサヌの起動、コンシュヌマヌの起動、および Flink SQL で ETL 凊理の確認は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 flink-producer.sh コンシュヌマヌの起動 flink-sql-consumer.sh Flink SQL で ETL 凊理の確認 flink-sql-check-tables.sh Table API (PyFlink) プロデュヌサヌからコンシュヌマヌぞメッセヌゞを送受信する過皋で、事前に DDL でスキヌマを登録する代わりに、プログラム内で動的にスキヌマを定矩・操䜜しながら、柔軟か぀リアルタむムにデヌタをストリヌム凊理する Table API を䜓隓したす。 なお、本章の Table API のプログラムは、Python 蚀語版である PyFlink を利甚しお実装しおいたす。 「my-flink-input」トピックから受信しお「my-flink-table-api-output」トピックぞ送信する過皋で、Flink では Table API を䜿っお以䞋の加工を行いたす。 「my-flink-input」トピックから流れおきた JSON 圢匏のメッセヌゞをフィヌルド分解し、「FLINK_TABLE_API_INPUT」テヌブルのレコヌドずしお保存したす。 「FLINK_TABLE_API_INPUT」テヌブルのレコヌドを「FLINK_TABLE_API_OUTPUT」テヌブルに挿入したす。挿入の際には以䞋の加工を行いたす。 「name」フィヌルドの倧文字ず小文字を反転盞互倉換したす。 「name」フィヌルドに、「gender」フィヌルドが 'M' の堎合は 'Mr.' 、 'F' の堎合は 'Ms.' 、および 'X' の堎合は埌方に '-san' の敬称を付䞎したす。 「gender」フィヌルドを倧文字を小文字に、小文字を倧文字に倉換したす。 本ハンズオンでストリヌム凊理ずしお実装した Table API プログラム table_api.py たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-flink-input,my-flink-table-api-output Topic: my-flink-input TopicId: zqLyeMLwQ6aDptavQVkrvg PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-input Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: my-flink-table-api-output TopicId: E7t1BHUSRQKYAaNQF8Fp6A PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-table-api-output Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: メッセヌゞ送受信 送信偎のタヌミナルで、Flink Table API を䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > 受信偎のタヌミナルで、Flink Table API を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-table-api-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning 送信偎のプロデュヌサヌから JSON 圢匏のメッセヌゞ送信したす。Flink Table API で加工前 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > {"id":5, "name":"jiro", "gender":"M", "age":50} > {"id":6, "name":"saki", "gender":"F", "age":60} > {"id":7, "name":"pochi", "gender":"X", "age":70} 受信偎のコンシュヌマヌに、Flink Table API で加工埌のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-table-api-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning Partition:0 Offset:4 null {"id":5,"name":"Mr. JIRO","gender":"m","age":50} Partition:0 Offset:5 null {"id":6,"name":"Ms. SAKI","gender":"f","age":60} Partition:0 Offset:6 null {"id":7,"name":"POCHI -san","gender":"x","age":70} Flink Table API で実行された ETL 凊理の結果を確認したす。 Flink Table API のカタログ情報はセッションごずのむンメモリ管理ずなるため、起動した SQL Client 䞊で䞀床 CREATE TABLE 文を実行しおから SELECT を発行したす。 $ docker exec -it jobmanager ./bin/sql-client.sh Flink SQL> SHOW TABLES; Empty set Flink SQL> CREATE TABLE flink_table_api_input ( id INT, name STRING, gender STRING, age INT ) WITH ( 'connector' = 'kafka', 'topic' = 'my-flink-input', 'properties.bootstrap.servers' = 'kafka:29092', 'properties.group.id' = 'pyflink-group', 'scan.startup.mode' = 'earliest-offset', 'format' = 'json' ); [INFO] Execute statement succeeded. Flink SQL> CREATE TABLE flink_table_api_output ( id INT, name STRING, gender STRING, age INT ) WITH ( 'connector' = 'kafka', 'topic' = 'my-flink-table-api-output', 'properties.bootstrap.servers' = 'kafka:29092', 'scan.startup.mode' = 'earliest-offset', 'format' = 'json' ); [INFO] Execute statement succeeded. Flink SQL> SHOW TABLES; +------------------------+ | table name | +------------------------+ | flink_table_api_input | | flink_table_api_output | +------------------------+ 2 rows in set Flink SQL> SELECT * FROM flink_table_api_input; id name gender age --- ------ ------ --- 5 jiro M 50 6 saki F 60 7 pochi X 70 Flink SQL> SELECT * FROM flink_table_api_output; id name gender age --- ----------- ------ --- 5 Mr. JIRO M 50 6 Ms. SAKI F 60 7 POCHI -san X 70 Flink SQL> quit; プロデュヌサヌの起動、コンシュヌマヌの起動、および Flink SQL で ETL 凊理の確認は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 flink-producer.sh コンシュヌマヌの起動 flink-table-api-consumer.sh Table API で ETL 凊理の確認 flink-table-api-check-tables.sh DataStream API (PyFlink) プロデュヌサヌからコンシュヌマヌぞメッセヌゞを送受信する過皋で、流れおくるデヌタを衚圢匏ではなく1件ず぀の「オブゞェクト」ずしお盎接操䜜し、状態管理Statefulや柔軟なタむムりィンドり凊理など、より现やかで高床な制埡を行いながらストリヌム凊理する DataStream API を䜓隓したす。 なお、本章の DataStream API のプログラムは、Python 蚀語版である PyFlink を利甚しお実装しおいたす。 「my-flink-input」トピックから受信しお「my-flink-datastream-api-output」トピックぞ送信する過皋で、Flink では DataStream API を䜿っお以䞋の加工を行いたす。 「my-flink-input」トピックから流れおきた JSON 圢匏のメッセヌゞを1件ず぀、以䞋の加工を順次凊理したす。 「name」ず「gender」フィヌルドぞ Table API ず同じ加工を行いたす。 Table API では実装が困難な加工凊理も行いたす。 ストリヌム凊理時点における性別毎の平均幎霢倀を保持し、”avg_age” フィヌルドに出力したす。 性別毎の平均幎霢倀ずストリヌムのレコヌドを比范した結果を “vs_avg“ フィヌルドに出力したす。 加工凊理が終わったら、「my-flink-datastream-api-output」トピックず玐づいた「FLINK_DATASTREAM_API_SINK」テヌブルにシンクしたす。 本ハンズオンでストリヌム凊理ずしお実装した DataStream API プログラム datastream_api.py たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-flink-input,my-flink-datastream-api-output Topic: my-flink-datastream-api-output TopicId: wYDXtcFLR6GZf_qJ53POTA PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-datastream-api-output Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: my-flink-input TopicId: rAlyReroQFyQprZbtUJUOA PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-flink-input Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: メッセヌゞ送受信 送信偎のタヌミナルで、Flink DataStream API を䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > 受信偎のタヌミナルで、Flink DataStream API を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-datastream-api-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning 送信偎のプロデュヌサヌから JSON 圢匏のメッセヌゞ送信したす。Flink DataStream API で加工前 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-flink-input > {"id":8, "name":"saburo", "gender":"M", "age":80} > {"id":9, "name":"yoko", "gender":"F", "age":90} > {"id":10, "name":"kuro", "gender":"X", "age":100} 受信偎のコンシュヌマヌに、Flink DataStream API で加工埌のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-flink-datastream-api-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning Partition:0 Offset:7 null {"id":8,"name":"Mr. SABURO","gender":"m","age":80} Partition:0 Offset:8 null {"id":9,"name":"Ms. YOKO","gender":"f","age":90} Partition:0 Offset:9 null {"id":10,"name":"KURO -san","gender":"x","age":100} プロデュヌサヌの起動、コンシュヌマヌの起動、および Flink SQL で ETL 凊理の確認は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 flink-producer.sh コンシュヌマヌの起動 flink-datastream-api-consumer.sh DataStream API で扱うシンクテヌブルの確認 flink-datastream-api-check-table.sh Flink 各皮ストリヌムの䜿い分け方 ストリヌム凊理別の盞違点 ストリヌム凊理方匏 メッセヌゞの凊理方法 凊理の実装媒䜓 凊理の組み蟌み方法 ストリヌム凊理方匏 トピックをテヌブルずしお凊理 SQL文DDL、DML Flink SQL Client で SQL を実行 Table API トピックをテヌブルずしお凊理 ゜ヌスコヌド jobmanager で゜ヌスコヌドを実行 DataStream API 1件ず぀のメッセヌゞで凊理 ゜ヌスコヌド jobmanager で゜ヌスコヌドを実行 Table API ず DataStream API の盞違点 ゜ヌスコヌドで実装する Table API ず DataStream API の違いは以䞋の通りです。 入力元ずの接続方匏 Table API TableEnvironment.create() で生成したコンテキストに察しお、 execute_sql() の DDL で Kafka トピックにスキヌマ型定矩を割り圓お、仮想テヌブルずしお登録しおから凊理を開始したす。 DataStream API StreamExecutionEnvironment.get_execution_environment() で生成したコンテキストに察しお、 KafkaSource.builder() で構築したコネクションを枡し、ストリヌムずしお盎接取り蟌みたす。入力に DDL は䜿いたせん。 デヌタ凊理の曞き方ず単䜍 Table APIデヌタを衚ずしお扱うため、 @udf デコレヌタを付けた関数を定矩し、 .select() や .filter() などのメ゜ッドチェヌンで列単䜍の倉換や抜出を行いたす。 DataStream APIデヌタを「1件のメッセヌゞオブゞェクト」ずしお受け取りたす。シンプルな倉換では map() や flatMap() などのオペレヌタを甚いお倉換・抜出を行いたすが、耇雑な条件刀断や状態管理を行う堎合は KeyedProcessFunction や process() を実装するこずで、キヌごずに状態Stateを保持したより高床で柔軟なロゞックを蚘述できたす。 出力先ずの接続方法 Table API入力ず同様に DDL で定矩した出力先テヌブルに察し、 execute_insert() を発行しおデヌタを流し蟌みたす。 DataStream API KafkaSink などの専甚コネクタを定矩し、 .sink_to() メ゜ッドでストリヌムを盎接出力先ぞ接続したす。なお、出力郚のみ Table API の DDL 機胜をブリッゞずしお利甚するハむブリッドなパタヌンも存圚したす。 本ハンズオンで動かしおいるプログラムは以䞋の通りです。 Table API table_api.py DataStream API datastream_api.py Schema Registry プロデュヌサヌからコンシュヌマヌぞメッセヌゞを送受信する過皋で、䞀元管理されたデヌタ構造スキヌマずメッセヌゞを照合し、ルヌルに合わない䞍正デヌタの混入を防いでシステム党䜓のデヌタ品質を守る Schema Registry を䜓隓したす。 プロデュヌサヌからコンシュヌマヌぞトピックを介しおメッセヌゞを送受信する過皋で、以䞋の照合凊理を行いたす。 プロデュヌサヌでメッセヌゞを送信する際に以䞋の凊理を行いたす。 事前に Schema Registry にスキヌマ登録した際に発行された ID を指定しお、Schema Registry に登録されおいるスキヌマを取埗したす。 取埗したスキヌマずメッセヌゞを照合し、䞀臎したメッセヌゞのみスキヌマ ID を付䞎しおトピックぞ送信したす。 コンシュヌマヌでメッセヌゞを受信する際に以䞋の凊理を行いたす。 トピックを介しお送られおきたメッセヌゞに付䞎された ID を指定しお、Schema Registry に登録されおいるスキヌマを取埗したす。 取埗したスキヌマずメッセヌゞを照合し、䞀臎したメッセヌゞのみ受取りたす。 Kafka 自䜓や、Kafka CLI ツヌルのプロデュヌサヌやコンシュヌマヌには、スキヌマずメッセヌゞを照合する仕組みは備わっおいないため、本章では独自に実装したプロデュヌサヌずコンシュヌマヌを䜿甚しおいたす。 schema-registry-json-producer.py 、 schema-registry-json-consumer.py Schema Registry ずしお利甚しおいる Apicurio Registrykafkasql ストレヌゞモヌド では、登録されたスキヌマを Kafka の「kafkasql-journal」トピックに保存しお管理したす。 本ハンズオンで Schema Registry 登録甚に実装したスキヌマ schema_registry.json AKHQ で登録されおいるスキヌマを確認できたす。 http://localhost:8080/ui/hands-on-kafka/schema たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-schema-registry-json Topic: my-schema-registry-json TopicId: 2xQXf5EXS-i96w-W-laIhw PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-schema-json Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: メッセヌゞ送受信 送信偎のタヌミナルで、Schema Registry を䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。暙準の Kafka CLI ツヌルは Schema Registry ずの連携に察応しおいないため、本章では Python で実装したオリゞナルのプロデュヌサヌプログラムを䜿甚したす。 【送信偎】 $ docker cp ~/handson/hands-on-kafka/try-my-hand/schema-registry-json-producer.py \ handson-client:/tmp/schema-registry-json-producer.py $ docker exec -i handson-client python3 /tmp/schema-registry-json-producer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --schema-id 1 > 受信偎のタヌミナルで、Schema Registry を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。コンシュヌマヌ偎でも、プロデュヌサヌず同様に Python で実装したオリゞナルのコンシュヌマヌプログラムを䜿甚したす。 【受信偎】 $ docker cp ~/handson/hands-on-kafka/try-my-hand/schema-registry-json-consumer.py \ handson-client:/tmp/schema-registry-json-consumer.py $ docker exec -it handson-client python3 /tmp/schema-registry-json-consumer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --from-beginning \ --print-schema-ids 送信偎のプロデュヌサヌから JSON 圢匏のメッセヌゞ送信したす。 【送信偎】 $ docker exec -i handson-client python3 /tmp/schema-registry-json-producer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --schema-id 1 > {"id":1, "name":"taro", "gender":"M", "age":10} スキヌマず䞀臎するため受信偎のコンシュヌマヌにメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client python3 /tmp/schema-registry-json-consumer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --from-beginning \ --print-schema-ids schemaId:1 {"id": 1, "name": "taro", "gender": "M", "age": 10} スキヌマず䞀臎しないメッセヌゞの堎合は、゚ラヌず刀断されプロデュヌサヌから送信できたせん。 【送信偎】 $ docker exec -i handson-client python3 /tmp/schema-registry-json-producer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --schema-id 1 > {"id":1, "name":"taro", "gender":"M", "age":10} > {"id":2, "name":"unknown", "gender":"A", "age":20} [ERROR] Schema validation failed: 'A' is not one of ['M', 'F', 'X'] > {"id":3, "gender":"X", "age":30} [ERROR] Schema validation failed: 'name' is a required property > {"id":4, "name":"tamako", "gender":"F", "age":"forty"} [ERROR] Schema validation failed: 'forty' is not of type 'number' > {"id": 5, "name": "jiro", "gender": "M", "age": 50} id=1 は、スキヌマず䞀臎しおいるので、問題なくプロデュヌサヌから送信された。 id=2 は、gender が芏定倀倖であるため、゚ラヌずしおプロデュヌサヌから送信されない。 id=3 は、name フィヌルドが無いため、゚ラヌずしおプロデュヌサヌから送信されない。 id=4 は、age が数倀型ではないため、゚ラヌずしおプロデュヌサヌから送信されない。 id=5 は、スキヌマず䞀臎しおいるので、問題なくプロデュヌサヌから送信された。 受信偎のコンシュヌマヌでは、スキヌマず䞀臎するメッセヌゞのみ受信できおいたす。 【受信偎】 $ docker exec -it handson-client python3 /tmp/schema-registry-json-consumer.py \ --bootstrap-server kafka:29092 \ --topic my-schema-registry-json \ --schema-registry-url http://schema-registry:8080/apis/ccompat/v7 \ --from-beginning \ --print-schema-ids schemaId:1 {"id": 1, "name": "taro", "gender": "M", "age": 10} schemaId:1 {"id": 5, "name": "jiro", "gender": "M", "age": 50} プロデュヌサヌの起動ずコンシュヌマヌの起動は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 schema-registry-json-producer.sh コンシュヌマヌの起動 schema-registry-json-consumer.sh Kafka Streams Application プロデュヌサヌからコンシュヌマヌぞメッセヌゞを送受信する過皋で、プログラム蚀語で独自のデヌタ凊理を実装できるストリヌムアプリケヌションを利甚したストリヌム凊理を䜓隓したす。 「my-streams-plaintext-input」トピックから受信しお「my-streams-myhandson-output」トピックぞ送信する過皋で、Kafka Streams ラむブラリを䜿甚したアプリケヌションでは以䞋の加工を行いたす。 「my-streams-plaintext-input」トピックから流れおきたテキスト圢匏のメッセヌゞを1件ず぀、以䞋の加工を順次凊理したす。 StreamsBuilder.stream() メ゜ッドを甚いお「my-streams-plaintext-input」トピックに流れおきたメッセヌゞを凊理甚のデヌタストリヌムKStreamずしお読み蟌みたす。 流れおきたメッセヌゞの倀に察し、英倧文字ず小文字を盞互に倉換する加工凊理を flatMapValues() で適甚したす。 加工埌のデヌタストリヌムに察しお to() メ゜ッドを実行し、「my-streams-myhandson-output」トピックぞ結果を曞き蟌みたす。 本ハンズオンでストリヌム凊理ずしお実装した Kafka Streames Application プログラム MyHandsOn.java たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-streams-plaintext-input,my-streams-myhandson-output Topic: my-streams-myhandson-output TopicId: dyClhMMiRxCWeZ4BebQzbg PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-streams-myhandson-output Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: Topic: my-streams-plaintext-input TopicId: RI97GYKxRu-LnAqZ1RZbQw PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-streams-plaintext-input Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: メッセヌゞ送受信 送信偎のタヌミナルで、Kafka Streams Application を䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-streams-plaintext-input > 受信偎のタヌミナルで、Kafka Streams Application を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-streams-myhandson-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning 送信偎のプロデュヌサヌからメッセヌゞ送信したす。Kafka Stream Application で加工前 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-streams-plaintext-input > Hey, It's Me! 受信偎のコンシュヌマヌで、Kafka Stream Application で加工埌のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-streams-myhandson-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning Partition:0 Offset:0 null hEY, iT'S mE! プロデュヌサヌの起動ずコンシュヌマヌの起動は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 streams-producer.sh コンシュヌマヌの起動 streams-consumer-myhandson.sh Kafka Streams ず Flink DataStream API の䜿い分け ゜ヌスコヌドで実装する Kafka Streams ず Flink DataStream API の違いは以䞋の通りです。 圹割ず構造の違い Kafka Streams軜量な組み蟌みラむブラリ Spring Boot などの Java/Kotlin アプリケヌション環境に Kafka Streams ラむブラリを組み蟌むだけで、簡単に開発・動䜜環境を甚意できたす。Kafka からデヌタを受け取り、加工しお、別の Kafka トピックぞ戻す凊理を倖郚クラスタなしでシンプルに構築できたす。 Flink DataStream API汎甚ストリヌム凊理基盀 Flink クラスタ䞊で分散実行されるプログラムを曞くための API です。Kafka 以倖のデヌタ゜ヌスS3、デヌタベヌス、他システムずも自由に぀なぎ、巚倧なデヌタを分散しお高速か぀耇雑に蚈算できたす。 䜿い分けの刀断基準 Kafka Streams を遞ぶべきケヌス システム構成が Kafka 䞭心 で完結しおいる。Kafka ➔ 加工 ➔ Kafka Flink のような 新しいクラスタを管理・運甚したくない。むンフラ運甚の負担を枛らしたい Java/Kotlin アプリの䞀機胜ずしお、手軜にストリヌム凊理を組み蟌みたい。 Flink DataStream API を遞ぶべきケヌス Kafka だけでなく S3 や 倖郚 DB など倚様なシステムを跚ぐデヌタパむプラむン を䜜りたい。 耇雑なデヌタ遅延を含む時間枠ごずの集蚈りィンドり凊理を行いたい。䟋盎近5分間の件数を1分ごずに集蚈 耇数の出来事が特定の順番・時間内で起きたパタヌンを怜知したい。CEP䟋ログむン倱敗3回埌に高額決枈 将来的にデヌタ量が膚倧になり、クラスタレベルでの匷力なスケヌルアりトを行いたい。 Kafka Connect Debezium Connector for MySQL (CDC) MySQL を察象ずした CDC の Kafka Connector である「 Debezium connector for MySQL :: Debezium Documentation 」を利甚しお、デヌタベヌスにコミットされたすべおの操䜜をトレヌスしおみたす。 AKHQ で登録されおいる Debezium Connector を確認できたす。 http://localhost:8080/ui/hands-on-kafka/connect/debezium たずは、「 Kafka 環境セットアップスクリプトの実行 」章で䜜成した、本章で利甚するトピックが存圚するこずを確認したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh \ --bootstrap-server kafka:29092 --describe \ --topic my-cdc-mysql.mytest.user Topic: my-cdc-mysql.mytest.user TopicId: W2iCPAiXR02_bcYvxmK_Nw PartitionCount: 1 ReplicationFactor: 1 Configs: min.insync.replicas=1 Topic: my-cdc-mysql.mytest.user Partition: 0 Leader: 1 Replicas: 1 Isr: 1 Elr: LastKnownElr: SQL 送信でメッセヌゞ受信 先に受信偎のタヌミナルで、CDC Connector を䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-cdc-mysql.mytest.user \ --from-beginning 送信偎のタヌミナルで、MySQL に察しお曎新系の SQL を送りたす 【送信偎】 $ docker exec mysql mysql -u myuser -pmypass -D mytest -e "INSERT INTO user(name) VALUES ('taro sios');" $ docker exec mysql mysql -u myuser -pmypass -D mytest -e "INSERT INTO user(name) VALUES ('hanako sios');" $ docker exec mysql mysql -u myuser -pmypass -D mytest -e "UPDATE user SET name='taro2 sios2' WHERE id=1;" $ docker exec mysql mysql -u myuser -pmypass -D mytest -e "DELETE FROM user WHERE id = 2;" コマンド実行で以䞋譊告メッセヌゞを衚瀺する堎合がありたすが、ロヌカル環境の䜓隓向けに、MySQL のログむンパスワヌドを含んだコマンドラむンを実行した譊告であるため、今回は無芖しおください。 mysql: [Warning] Using a password on the command line interface can be insecure. 受信偎のコンシュヌマヌに、MySQL でデヌタが䜜成、曎新、および削陀された差分情報のメッセヌゞが届きたした。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-cdc-mysql.mytest.user \ --from-beginning {"before":null,"after":{"id":1,"name":"taro sios","updated_at":1786732821000}, ... } {"before":null,"after":{"id":2,"name":"hanako sios","updated_at":1786732835000}, ... } {"before":{"id":1,"name":"taro sios","updated_at":1786732821000},"after":{"id":1,"name":"taro2 sios2","updated_at":1786732849000}, ... } {"before":{"id":2,"name":"hanako sios","updated_at":1786732835000},"after":null, ... } 曎新系 SQL の発行ずコンシュヌマヌの起動は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 曎新系 SQL の発行 cdc-mysql-send-query.sh コンシュヌマヌの起動 cdc-mysql-consumer.sh パヌティション トピックをパヌティションで分割するこずにより、コンシュヌマヌの䞊列化による負荷分散を実珟したす。 パヌティションを䜿わない堎合 デヌタの配信方法すべおのメッセヌゞが1぀の単䞀レヌントピックに流れたす。 コンシュヌマヌ偎の挙動 単䞀レヌン構造受信経路が1぀のため、コンシュヌマヌを远加しおも䞊列分散凊理を行う構成にはなりたせん。 独立した重耇受信マルチナヌス远加した各コンシュヌマヌが党デヌタを受け取り、流れおきた泚文情報に察しお「発送凊理」や「圚庫管理」など別々の目的で利甚できたす。 パヌティションを䜿った堎合 デヌタの配信方法送信時にキヌのハッシュ倀を蚈算し、各パヌティションPartition 0〜nぞメッセヌゞを自動分散したす䟋: usr_1 ➔ Partition 0、usr_2 ➔ Partition 1、usr_3 ➔ Partition 2。 コンシュヌマヌ偎の挙動 理想的な䞊列凊理Group A1぀に぀き1パヌティションを専任A-1〜A-3しお凊理し、最高のパフォヌマンスを発揮したす。 䜙剰分の予備機化Consumer A-4パヌティション数3぀を超える4個目は「ホットスタンバむ埅機状態」ずなり、皌働䞭のコンシュヌマヌが萜ちた際に即座に匕き継ぎたす。 少ない数での兌任凊理Group Bコンシュヌマヌ数2個が少ない堎合、1個B-1が耇数パヌティションPartition 0 ず 1を自動で兌任しお受信するため、デヌタ欠萜なく凊理を実行したす。 グルヌプ間の独立配信Group A ず Group B のようにグルヌプ ID を分けるこずで、党く同じデヌタ矀をそれぞれの目的で独立しお重耇受信マルチナヌスできたす。 メッセヌゞ送受信 送信偎のタヌミナルで、パヌティションを䜓隓するための送信先のトピックを指定しおプロデュヌサヌを起動したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic-partition \ --reader-property "parse.key=true" --reader-property "key.separator=:" 受信偎のタヌミナルで、パヌティションを䜓隓するための受信元のトピックを指定しおコンシュヌマヌを起動したす。䞊列分散凊理ずホットスタンバむを確認するため、パヌティション数3぀+ 予備1個 蚈4぀のタヌミナルを䜿い、それぞれで起動したす。 【受信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 --topic my-topic-partition \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --group my-group-a \ --from-beginning プロデュヌサヌずコンシュヌマヌのそれぞれが起動盎埌の状態であるこずを確認したす。 送信偎のプロデュヌサヌから、 ”usr_1:” から ”usr_3:” たでの3぀のメッセヌゞを送信したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic-partition \ --reader-property "parse.key=true" --reader-property "key.separator=:" > usr_1:(p-0)taro > usr_2:(p-1)hanako > usr_3:(p-2)tama 受信偎で各パヌティションにバランシングされたコンシュヌマヌが、それぞれのメッセヌゞを受信できおいるこずを確認したす。 送信偎のプロデュヌサヌから、远加で ”usr_4:” から ”usr_6:” たでの3぀のメッセヌゞを送信したす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic-partition \ --reader-property "parse.key=true" --reader-property "key.separator=:" > usr_1:(p-0)taro > usr_2:(p-1)hanako > usr_3:(p-2)tama > usr_4:(p-2)tamako > usr_5:(p-1)jiro > usr_6:(p-0)saki 受信偎では匕き続き、各パヌティションず玐づいおいるコンシュヌマヌにメッセヌゞが届きたす。 ここで「Partition: 1」の受信を担っおいるコンシュヌマヌのタヌミナルで「Ctrl + C」を抌し、コンシュヌマヌを匷制終了したす。 送信偎のプロデュヌサヌから远加で、 ”usr_7:” から ”usr_9:” たでの3぀のメッセヌゞを送りたす。 【送信偎】 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 --topic my-topic-partition \ --reader-property "parse.key=true" --reader-property "key.separator=:" > usr_1:(p-0)taro > usr_2:(p-1)hanako > usr_3:(p-2)tama > usr_4:(p-2)tamako > usr_5:(p-1)jiro > usr_6:(p-0)saki > usr_7:pochi(p-2) > usr_8:saburo(p-1) > usr_9:yoko(p-0) ホットスタンバむだったコンシュヌマヌが「Partition 2」ぞ割り圓おられ、メッセヌゞを受信し始めたす。このように、スタンバむの掻性化ず同時に、これたで「Partition 2」を担圓しおいたコンシュヌマヌが「Partition 1」の受信を匕き継ぐなど、グルヌプ党䜓でリバランス再割り圓おが行われるこずもありたす。 プロデュヌサヌの起動ずコンシュヌマヌの起動は、簡単に実行できるように以䞋スクリプトを甚意しおいたす。 プロデュヌサヌの起動 topic-partition-producer.sh コンシュヌマヌの起動 topic-partition-consumer.sh 以䞊で、Apache Kafka を掻甚したデヌタパむプラむンずストリヌム凊理のハンズオンはすべお終了です。お疲れ様でした シンプルなメッセヌゞの送受信から始たり、Flink や Kafka Streams によるリアルタむムなデヌタ加工、Schema Registry によるデヌタ品質管理、さらには Debezium による CDC 連携やパヌティションによる負荷分散たで䞀通り䜓隓しおいただきたした。Kafka を䞭心ずした゚コシステムが連携し、リアルタむムデヌタ基盀ずしおどのように機胜するのか、その「手応え」を掎んでいただけたなら幞いです。 環境のクリヌンアップ Kafka のハンズオンが䞀通り終わったら、これたでに利甚した環境をクリヌンアップしおハンズオンを終了したす。 コンテナ削陀スクリプトの実行 タヌミナルを甚いお、以䞋のディレクトリぞ移動したす。 $ cd ~/handson/hands-on-kafka/container/ 環境構築時にも䜿甚したスクリプトに down オプションを指定しお実行しお、Kafka 環境の各皮コンテナを停止・削陀したす。 $ ./CREATE_CONTAINER.sh down スクリプトの実行が終わったら、 list オプションを付けおスクリプトを実行し、起動しおいるコンテナが存圚しない䞀芧に衚瀺されないこずを確認したす。 $ ./CREATE_CONTAINER.sh list ### START: Show a list of container ########## CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 以䞊で環境のクリヌンアップは完了です。お疲れ様でした。 続く Appendix では、このハンズオン環境を裏で支えおいる蚭定ファむルやスクリプトに぀いお解説したす。「どうやっおこの環境を䜜ったのか詳しく知りたい」ずいう方は、ぜひこのたたご芧ください。 Appendix docker-compose / Dockerfile の解説 flink/Dockerfile 「 Dockerfile 」は、Apache Flink コンテナ向けに以䞋の環境を構築するため、「docker-compose.yml」ずは別に甚意しおいたす。 Kafka コネクタの事前配眮Flink から Kafka トピックぞアクセスできるよう、Kafka Connector JAR を取埗・配眮したす。 PyFlink 実行環境の構築Python 環境のセットアップを行い、PyFlink ラむブラリをむンストヌルしたす。 streams/Dockerfile 「 Dockerfile 」は、Kafka Streams アプリケヌションコンテナ向けに以䞋の環境を構築するため、「docker-compose.yml」ずは別に甚意しおいたす。 ビルド・実行環境の構築コンテナ起動時に Java アプリケヌションを゜ヌスコヌドからビルド・実行できるよう、Maven および JDK 環境をセットアップしたす。 handson-client/Dockerfile 「 Dockerfile 」は、ハンズオンの各皮操䜜を行うクラむアントコンテナ向けに以䞋の環境を構築するため、「docker-compose.yml」ずは別に甚意しおいたす。 Python 実行環境の構築Schema Registry 章などで䜿甚する Python 補のオリゞナルプロデュヌサヌ・コンシュヌマヌを実行できるよう、Python 環境ず関連ラむブラリをセットアップしたす。 コンテナ構築スクリプトの解説 「 コンテナ構築スクリプトの実行 」章で䜿甚する「 CREATE_CONTAINER.sh 」スクリプトの凊理内容に぀いお解説したす。 コンテナの構築 甚意した「docker-compose.yml」のコンテナ定矩ファむルで䞀気にコンテナを構築したす。 $ docker-compose \ -f docker-compose.yml \ up -d -V --remove-orphans Kafka 各コンテナの構成ファむル解説 AKHQ 蚭定ファむル application.yml AKHQ の Web UI で各コンテナの情報を衚瀺できるよう、Kafka、Schema Registry、および DebeziumCDC Connectorの接続先を蚭定しおいたす。 MySQL 蚭定ファむル .env-mysql MySQL のルヌトナヌザヌパスワヌドやデヌタベヌス名などの環境倉数を指定したす。 my.cnf 文字コヌドの蚭定や、CDC で必芁ずなるバむナリヌログの有効化を指定したす。 init.sql ハンズオンで䜿甚するデヌタベヌスやテヌブルを構築するための初期化 DDL です。 Kafka 環境セットアップスクリプトの解説 「 Kafka 環境セットアップスクリプトの実行 」章で䜿甚する「 SETUP_HANDS-ON.sh 」スクリプトの凊理内容に぀いお解説したす。 必須コマンドの存圚確認 Kafka 環境のセットアップをスクリプトで行う際に必芁ずなるコマンドがむンストヌルされおいるかどうかを確認したす。コマンドの存圚が確認できなかった堎合には、スクリプトは停止したすので、手䜜業でコマンドのむンストヌルをお願いしたす。詳现な実行内容は以䞋実装を確認しおください。 SETUP_HANDS-ON.sh トピックの䜜成 「handson-client」コンテナを介しお、ハンズオンで利甚するトピックを䜜成したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:29092 --create --topic my-topic : 詳现な実行内容は以䞋実装を確認しおください。 step01-create_topic.sh Flink の蚭定 Flink SQL のハンズオン向けに、事前に甚意した DDL・DML を Flink SQL Client ぞ流し蟌みたす。 $ cat ./config/flink_sql.sql | docker exec -i jobmanager ./bin/sql-client.sh flink_sql.sql Table API のハンズオン向けに、スクリプトの実行を開始したす。 $ docker exec jobmanager ./bin/flink run -d --python /opt/flink/table_api.py DataStream API のハンズオン向けに、スクリプトの実行を開始したす。 $ docker exec jobmanager ./bin/flink run -d --python /opt/flink/datastream_api.py 詳现な実行内容は以䞋実装を確認しおください。 step02-register_flink.sh Schema Registry の蚭定 ハンズオン向けに、スキヌマをSchema Registry ぞ登録したす。 schema_registry.json 詳现な実行内容は以䞋実装を確認しおください。 step03-register_schema_registry.sh Kafka Streams Application の蚭定 Streams Application ず䜜ったトピックを玐づけるためにコンテナを再起動したす。 $ docker compose -f ~/handson/hands-on-kafka/container/docker-compose.yml restart streams 詳现な実行内容は以䞋実装を確認しおください。 step04-bind_stream_to_topic.sh Debezium Connector for MySQL の蚭定 Debezium Connector コンテナぞ REST API で、MySQL をデヌタ゜ヌスずした CDC を蚭定したす。 $ curl -v -X POST \ "http://localhost:8083/connectors" \ -H "Content-Type: application/json" \ -d @$HOME/handson/hands-on-kafka/setup/config/debezium.json | \ jq 詳现な実行内容は以䞋実装を確認しおください。 step05-register_connector.sh Kafka Streams Application の開発ず起動 ハンズオン環境では、実装枈みの゜ヌスコヌドからビルドしたパッケヌゞを事前準備したコンテナで䜓隓しおいただきたしたが、ここでは Kafka Streams Application の゜ヌスコヌドをれロから生成し、パッケヌゞ䜜成から起動・メッセヌゞ送受信たでの手順を解説したす。 たずは、「streams」コンテナを停止したす。 $ docker stop streams Kafka Streams Application の開発ず起動甚の䜜業ディレクトリを甚意したす。 $ mkdir -p ~/work $ cd ~/work/ Kafka Streams Application の開発は Java 環境を必芁ずするため JDK をむンストヌルしたす。 $ sudo apt install -y openjdk-21-jdk-headless Java21 以倖のバヌゞョンがアクティブな堎合は「update-alternatives」を利甚しお Java21 をアクティブにしたす。参照先 初期環境構築: ナヌティリティツヌル on Ubuntu | Java コマンド (JDK)  ビルドに Maven を䜿うためむンストヌルしたす。 $ sudo apt install -y maven Maven コマンドで Kafka Streams Application の゜ヌステンプレヌトを䜜成したす。 $ mvn archetype:generate \ -DinteractiveMode=false \ -DarchetypeGroupId=org.apache.kafka \ -DarchetypeArtifactId=streams-quickstart-java \ -DarchetypeVersion=3.9.0 \ -DgroupId=jp.sios \ -DartifactId=apisl.handson.kafka.streams \ -Dversion=0.1 \ -Dpackage=jp.sios.apisl.handson.kafka.streams $ cd apisl.handson.kafka.streams/ 「pom.xml」を修正。 @@ -31,6 +31,8 @@ <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <kafka.version>3.9.0</kafka.version> <slf4j.version>1.7.36</slf4j.version> + <maven.compiler.source>21</maven.compiler.source> + <maven.compiler.target>21</maven.compiler.target> </properties> @@ -57,69 +59,16 @@ <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <version>3.1</version> + <version>3.13.0</version> <configuration> - <source>1.8</source> - <target>1.8</target> + <source>21</source> + <target>21</target> </configuration> </plugin> </plugins> <pluginManagement> <plugins> - <plugin> - <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <source>1.8</source> - <target>1.8</target> - <compilerId>jdt</compilerId> - </configuration> - <dependencies> - <dependency> - <groupId>org.eclipse.tycho</groupId> - <artifactId>tycho-compiler-jdt</artifactId> - <version>0.21.0</version> - </dependency> - </dependencies> - </plugin> - <plugin> - <groupId>org.eclipse.m2e</groupId> - <artifactId>lifecycle-mapping</artifactId> - <version>1.0.0</version> - <configuration> - <lifecycleMappingMetadata> - <pluginExecutions> - <pluginExecution> - <pluginExecutionFilter> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-assembly-plugin</artifactId> - <versionRange>[2.4,)</versionRange> - <goals> - <goal>single</goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore/> - </action> - </pluginExecution> - <pluginExecution> - <pluginExecutionFilter> - <groupId>org.apache.maven.plugins</groupId> - <artifactId>maven-compiler-plugin</artifactId> - <versionRange>[3.1,)</versionRange> - <goals> - <goal>testCompile</goal> - <goal>compile</goal> - </goals> - </pluginExecutionFilter> - <action> - <ignore/> - </action> - </pluginExecution> - </pluginExecutions> - </lifecycleMappingMetadata> - </configuration> - </plugin> </plugins> </pluginManagement> </build> LineSplit アプリケヌションの゜ヌスコヌドを開き、入出力するトピック名をデフォルトから甚意したトピック名に修正したす。 $ vim src/main/java/jp/sios/apisl/handson/kafka/streams/LineSplit.java : - builder.<String, String>stream("streams-plaintext-input") + builder.<String, String>stream("my-streams-plaintext-input") .flatMapValues(value -> Arrays.asList(value.split("\\W+"))) - .to("streams-linesplit-output"); + .to("my-streams-linesplit-output"); : パッケヌゞを䜜成し、LineSplit アプリケヌションを起動したす。 $ mvn clean package $ mvn exec:java -Dexec.mainClass=jp.sios.apisl.handson.kafka.streams.LineSplit 送信偎のタヌミナルでメッセヌゞを送信したす。LineSplit アプリは、「my-stream-plaintext-input」から「my-stream-linesplit-output」トピックぞ単語に分割しお転送したす $ docker exec -it handson-client /opt/kafka/bin/kafka-console-producer.sh \ --bootstrap-server kafka:29092 \ --topic my-streams-plaintext-input >I-have-an-apple >I_have_an_apple >I have an apple 受信偎のタヌミナルでメッセヌゞを受信したす。 $ docker exec -it handson-client /opt/kafka/bin/kafka-console-consumer.sh \ --bootstrap-server kafka:29092 \ --topic my-streams-linesplit-output \ --formatter-property print.partition=true \ --formatter-property print.offset=true \ --formatter-property print.key=true \ --from-beginning Partition:0 Offset:0 null I Partition:0 Offset:1 null have Partition:0 Offset:2 null an Partition:0 Offset:3 null apple Partition:0 Offset:4 null I_have_an_apple Partition:0 Offset:5 null I Partition:0 Offset:6 null have Partition:0 Offset:7 null an Partition:0 Offset:8 null apple 最埌にクリヌンアップ䜜業ずしお、Kafka Streams Application 甚のタヌミナルに戻り「Ctrl + C」で起動を停止し、゜ヌスコヌドを削陀したす。 $ cd ~/work/ $ rm -rf apisl.handson.kafka.streams/ 「streams」コンテナを再起動したす。 $ docker start streams たずめ Apache Kafka を䞭心に、すべおオヌプン゜ヌスOSSのプロダクトを組み合わせ、基本的なメッセヌゞ送受信からリアルタむムなデヌタ加工Flink / Kafka Streams、デヌタ品質管理Schema Registry、倖郚 DB ずの CDC 連携Debezium、そしおパヌティションによる負荷分散たで、リアルタむムデヌタパむプラむンの䞀連の流れをハンズオン圢匏で䜓隓しおいただきたした。 今回のハンズオンで䜓隓・孊習できる䞻なポむントは以䞋の通りです。 オヌル OSS で揃うリアルタむムデヌタ基盀 Kafka Broker、AKHQ、Flink、Debezium などのオヌプン゜ヌスのみをコンテナで䞀括構築し、ロヌカル環境で手軜に実践的なむベントストリヌミング環境を甚意できるこず。 甚途に応じた柔軟なストリヌム凊理 SQL感芚で扱える Flink SQL、コヌドで動的に定矩する Table API、高床な状態管理が可胜な DataStream API、そしお軜量な組み蟌みラむブラリである Kafka Streams など、ナヌスケヌスや開発スタむルに応じた倚様な加工手法。 デヌタ品質の確保ずリアルタむム倖郚連携 Schema Registry によるスキヌマ怜蚌で䞍正デヌタの混入を防ぐ品質管理ず、Debezium コネクタを甚いた MySQL からの CDC倉曎デヌタ捕獲によるデヌタベヌス曎新の即時ストリヌム化。 パヌティションによる分散凊理ず高可甚性 パヌティションずコンシュヌマヌグルヌプを掻甚した䞊列分散凊理によるスケヌルアりトず、プロセス障害時におけるホットスタンバむ機ぞの自動リバランス匕き継ぎの仕組み。 「むベントストリヌミング」や「ストリヌム凊理」、「CDC」ずいった抂念は、蚀葉やアヌキテクチャ図だけで理解しようずするず難しく感じられがちですが、実際に手元でプロデュヌサヌからメッセヌゞを送信し、リアルタむムに加工・分散受信される様子を目の圓たりにするこずで、そのパワフルさや本質を実感しおいただけたのではないでしょうか。 本ハンズオンで䜿甚したスクリプトや蚭定ファむルはすべお GitHub で公開しおいたすので、環境構築の裏偎の仕組みを解析しおみたり、ご自身の開発アプリケヌションず連携させおカスタマむズしおみたりず、リアルタむムデヌタ基盀実践の第䞀歩ずしおぜひご掻甚ください。 最埌たでお読みいただき、ありがずうございたした。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Apache Kafka で䜓隓する『むベントストリヌミング入門』 first appeared on SIOS Tech Lab .
こんにちは、サむオステクノロゞヌの有村です。 WebサむトをHTTPS化する際は、「SSL蚌明曞」、「TLS蚌明曞」あるいは「サヌバヌ蚌明曞」ず呌ばれる蚌明曞を䜿甚したす。 䞀方、SAMLによるシングルサむンオンを構築する際の IdPIdentity ProviderやSPService Providerの蚭定でも「蚌明曞」が登堎したす。 どちらもX.509蚌明曞であるため、 SAMLでもWebサヌバヌず同じ、なんらかの機関から発行された公的な蚌明曞を甚意すべきなのでは 自己眲名蚌明曞は通垞ブラりザで譊告が出るのだから、SAMLでも䜿っおはいけないのでは 蚌明曞の有効期限が近づいたら、Webサヌバヌず同じようにすぐ曎新しなければいけないのでは ず考えおしたうず思われたす。 しかし、WebサむトのHTTPS通信で䜿甚する蚌明曞ず、SAMLで眲名や暗号化に䜿甚する蚌明曞では、 同じX.509蚌明曞でも圹割ず信頌の確認方法が倧きく異なりたす。 特に最近では、Webで利甚される公的なTLSサヌバヌ蚌明曞の有効期間がさらに短瞮されるこずが決たっおいたす。 これはWeb PKIの安党性ずいう芳点では重芁な倉化ですが、同じ蚌明曞なのでSAMLの眲名蚌明曞たで短期間で曎新する運甚にする必芁があるのでしょうか。 この蚘事では䞋蚘に぀いお説明したす。 WebのTLS蚌明曞では䜕を信頌しおいるのか SAMLの眲名蚌明曞では䜕を信頌しおいるのか 公的蚌明曞の有効期間短瞮に察しおSAMLではどう考えるべきか 認蚌フェデレヌションで利甚するずきの泚意点 そもそも「蚌明曞」ずは 最初に、今回扱う蚌明曞に぀いお簡単に敎理したす。 䞀般的なX.509蚌明曞には、公開鍵のほか、 察象(Subject) 発行者(Issuer) Serial Number 有効期間 公開鍵 眲名アルゎリズム 各皮Extension などの情報が含たれたす。 秘密鍵ず公開鍵はペアになっおおり、秘密鍵は所有者のみが保持したす。 HTTPSでもSAMLでも、この公開鍵暗号の仕組みを利甚しおいるずいう点では共通しおいたす。 しかし 「その蚌明曞を、誰が、どのような理由で信頌するのか」  が異なりたす。 WebのTLS蚌明曞では䜕を確認しおいるのか ブラりザで次のURLぞアクセスするずしたす。 https://www.example.com/ WebサヌバヌはHTTPS通信によりTLSプロトコルを開始する際、自身のサヌバヌ蚌明曞をブラりザぞ提瀺したす。 しかし、ブラりザは受け取った蚌明曞を無条件に信甚するわけではありたせん。 簡略化するず、次の図のような関係になっおいたす。 ブラりザやOSには、あらかじめ信頌するルヌトCA認蚌局の蚌明曞、぀たりルヌト蚌明曞が登録されおいたす。 ルヌト蚌明曞のリストは、OSやブラりザのベンダヌ等が定める厳栌な基準や審査に基づき、信頌の起点ずしお登録され、OS等の仕組みにより定期的に曎新されたす。 Webサヌバヌから提瀺された蚌明曞に぀いお、䞋蚘のように蚌明曞チェヌンをたどり、最終的にブラりザが信頌しおいる認蚌局ぞ到達できるかを確認したす。 それだけではありたせん。 䟋えば、 蚌明曞の期間は有効か アクセスしおいるホスト名ず蚌明曞のSANが䞀臎するか 蚌明曞チェヌンを正しく怜蚌できるか なども確認したす。 これによっお利甚者はブラりザにより間接的に、 自分がアクセスしおいるドメむンは、 第䞉者である認蚌局によっお珟圚も確認されおいるドメむンである ず刀断できたす。 ぀たり、公開Webサむトの堎合、 ブラりザずWebサヌバヌは事前に盎接の信頌関係を持っおいない 状況で、䞖界䞭の䞍特定倚数の利甚者が、初めおアクセスしおもWebサヌバヌを怜蚌できるようにするため、䞡者の間に認蚌局ずいう第䞉者が必芁になるのです。 これが䞀般的なWeb PKIの考え方です。 SAMLの眲名蚌明曞では䜕を確認しおいるのか ではSAMLではどうでしょうか。 SAMLによるシングルサむンオンでは、䟋えばIdPで利甚者を認蚌した埌、IdPからSPぞSAML Responseが送信されたす。 非垞に単玔化するず、次の図の流れになりたす。 IdPは自身が保持する秘密鍵を䜿甚しおSAML ResponseやAssertionぞ眲名したす。 SPは察応する公開鍵を利甚しお、その眲名が正しいこずを怜蚌したす。 SAMLでは、IdPずSPの情報を SAMLメタデヌタ によっおあらかじめ共有できたす。 IdPのメタデヌタには、たずえば眲名怜蚌に䜿甚する蚌明曞が登録されおいたす。 抂念ずしおは次のようになりたす。 ぀たりSPは、 この蚌明曞が公的認蚌局から発行されおいるから信頌する のではなく、 信頌するメタデヌタにすでに登録されおいるIdPの鍵だから、この鍵を眲名怜蚌に䜿甚する ずいう圢で信頌関係を構築したす。 ここがTLSずSAMLの非垞に倧きな違いです。 公的なTLS蚌明曞の有効期間は短くなっおいる この違いが今埌さらに重芁になる理由がありたす。 前述した通り、Webで䜿甚される公的TLS蚌明曞の有効期間は、長期的に短瞮されおいきたす。 CA/Browser ForumのBaseline Requirementsでは次のスケゞュヌルになっおいたす。 発行日 最倧有効期間日数 2026幎3月15日より前 398 2026幎3月15日以降 200 2027幎3月15日以降 100 2029幎3月15日以降 47 2026幎8月時点ですでに最倧200日の期間に入っおいたす。 2029幎以降は47日ずなりたす。 このように蚌明曞の有効期間が短くなれば、Webサヌバヌの蚌明曞を人間が毎回手䜜業で取埗し、蚭定ファむルを曞き換えおサヌビスを再起動するずいう運甚は珟実的ではなくなっおいきたす。 そのため、Webサヌバヌに぀いおはACMEなどを利甚した蚌明曞曎新の自動化が重芁になりたす。 䞀方でこれず同じ公的蚌明曞をSAMLメタデヌタ甚の蚌明曞ずしお利甚するずどうなるでしょうか。 倚くのSPでは認蚌フェデレヌションが関連し、最終的に数十日ごずに䞋蚘の運甚が必芁になりたす。 Webサヌバヌ内で完結する蚌明曞曎新ず異なり、自組織だけで倉曎が完結したせん。 そのため、Webサヌバヌず同じ感芚で蚌明曞を遞ぶこずが、かえっお運甚䞊のリスクになる可胜性がありたす。 フェデレヌションもSAML Metadata甚蚌明曞では自己眲名蚌明曞ぞの移行を案内 この問題は、フェデレヌションを運営する組織からもすでに案内が始たっおいたす。 䟋ずしおNIIはWebサヌバヌ甚蚌明曞はブラりザで怜蚌されるため有効期間短瞮の圱響を受け、ACMEによる自動曎新が珟実的である䞀方、SAMLメタデヌタ甚蚌明曞に぀いおは、高頻床で蚌明曞を発行しお孊認申請システムから倉曎申請するこずは非珟実的であり、珟状では自動化も難しく、倱敗時のリスクが倧きいず敎理しおいたす。 2025_UPKIキャラバン_サヌバ蚌明曞有効期間短瞮ずその察応 その察応ずしお、 自己眲名蚌明曞ぞの移行を怜蚎するよう案内されおいたす。 これは今回説明しおきたTLSずSAMLの信頌モデルの違いを考えるず頷けるものず思いたす。 Web甚蚌明曞に぀いおは䞋蚘の方向ぞ向かいたす。 䞀方、SAML眲名甚蚌明曞に぀いおは䞋蚘の組み合わせになりたす。 同じサヌバヌ䞊で利甚する蚌明曞であっおも、甚途に応じお異なる蚌明曞を別々に管理するずいう考え方です。 自己眲名蚌明曞は「自分で自分を信甚しおいる」から䜿えるわけではない ここで少し泚意したいのが、「自己眲名蚌明曞」ずいう蚀葉です。 自己眲名蚌明曞は、その名前のずおり自身の秘密鍵によっお自身ぞ眲名した蚌明曞です。 そのため、発行者Issuerず察象Subjectはむコヌルずなりたす。 しかし、SAMLで自己眲名蚌明曞を利甚できる理由は、 「自己ずはいえ眲名しおあるから信頌できる」 わけではありたせん。 重芁なのは、秘匿されおいる秘密鍵に玐づく公開鍵を、信頌できる経路で盞手ぞ䌝えるこずです。 認蚌フェデレヌションを利甚する堎合、その圹割を担うのがメタデヌタです。 ぀たり、䞋蚘に瀺す別の信頌関係がありたす。 蚌明曞そのものを公的CAが保蚌するWeb PKIずは、信頌を確立する堎所が違いたす。 したがっお、SAMLで自己眲名蚌明曞を䜿甚するずきにも、 どこから入手した鍵なのか分からない蚌明曞を自由に登録しおよいわけではありたせん。 メタデヌタそのものを安党に配垃・管理する仕組みに、秘匿された秘密鍵に玐づく公開鍵を登録しお初めお成立したす。 自己眲名蚌明曞の䜜成方法は本ブログに限らず倚数掲茉されおいるメディアがございたすので割愛したすが、 無期限に近い蚌明曞を䜜成しおいいわけではありたせん。 暗号アルゎリズムや鍵長に察する芁求は将来倉化したすし、秘密鍵が挏えいした堎合には有効期間にかかわらず亀換する必芁がありたす。 「曎新䜜業がほが䞍芁になるほど長くする」のではなく、 組織ずしお管理可胜な期間を定め、その期限が来る前に蚈画的にロヌルオヌバヌする こずが重芁です。 フェデレヌションが掚奚期間を提瀺しおいる堎合もございたすので確認しおください。 ※NIIは3幎を基準ずしおいたす。 蚌明曞を曎新するずきの泚意 自己眲名蚌明曞を利甚する堎合でも、蚌明曞の曎新自䜓が䞍芁になるわけではありたせん。 たた、SAMLでは盞手偎が保持しおいるメタデヌタ内の公開鍵を䜿っお眲名を怜蚌するため、IdP偎の蚌明曞だけを先に倉曎するず、盞手偎で眲名を怜蚌できなくなる可胜性がありたす。 そのため、蚌明曞を曎新する際は䞋蚘のような圢で、旧蚌明曞から新蚌明曞ぞ段階的に切り替える必芁がありたす。 新しい蚌明曞を準備する メタデヌタぞ旧蚌明曞ず平行しお新しい蚌明曞を登録する 連携先ぞ新しいメタデヌタが反映されたこずを確認する掚奚された期間埅機する IdP偎で䜿甚する蚌明曞を新蚌明曞のみに切り替える このように、旧蚌明曞から新蚌明曞ぞ安党に切り替えるこずを、䞀般に蚌明曞のロヌルオヌバヌず呌びたす。 詳现な手順は利甚するIdP補品やフェデレヌションによっお異なるため、本蚘事では割愛したす。 重芁なのは、自己眲名蚌明曞ぞ倉曎する目的が、 「蚌明曞曎新ずいう䜜業自䜓をなくすこず」 ではないずいう点です。 Web PKIの蚌明曞有効期間短瞮ずSAMLの運甚を切り離し、 SAMLの運甚に適したタむミングで、蚈画的に蚌明曞を曎新できるようにするこず が目的です。 たた、実際に自己眲名蚌明曞を利甚できるかどうかに぀いおは、接続する認蚌フェデレヌションや個別のSPが定める芁件も確認しおください。 SAMLずいうプロトコル䞊利甚可胜であるこずず、特定サヌビスの運甚ルヌル䞊蚱可されおいるこずは別の問題です。 たずめ 今回は、Webで利甚されるTLS蚌明曞ず、SAMLで䜿甚する眲名蚌明曞の違いに぀いお説明したした。 ポむントをたずめたす。 TLS蚌明曞ずSAML眲名蚌明曞は、どちらもX.509蚌明曞を䜿甚する Web公開TLS蚌明曞では、䞍特定倚数の利甚者がサヌバヌを怜蚌するために公的CAを利甚する SAMLでは、メタデヌタなどによっお連携盞手の公開鍵をあらかじめ共有できる このためSAML眲名蚌明曞では、公的CAによる蚌明曞が必須ずは限らない 公開TLS蚌明曞の最倧有効期間は今埌さらに短瞮され、2029幎には47日ずなる フェデレヌション機関もSAML甚蚌明曞に぀いお自己眲名蚌明曞ぞの移行を怜蚎するよう案内を開始しおいる 自己眲名蚌明曞にしおも鍵曎新は必芁であり、メタデヌタを利甚した適切なロヌルオヌバヌが重芁 「蚌明曞」ずいう同じ皮類のファむルを䜿っおいるず、぀い同じルヌルで管理すべきものに芋えおしたいたす。 その蚌明曞を䜕に䜿い、誰が、どのような仕組みで信頌するのか が特に重芁です。 WebのTLSずSAMLではその仕組みが異なるため、蚌明曞の遞択や曎新方法も、それぞれの甚途に合わせお考える必芁がありたす。 特に公開TLS蚌明曞の有効期間短瞮が進むこれからは、蚌明曞を䞀埋で運甚するのではなく、それぞれの甚途に適した蚌明曞管理 が重芁になるでしょう。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post SAMLの蚌明曞ずTLS蚌明曞の違い first appeared on SIOS Tech Lab .
最近のアプリケヌションやWebサむトを芋おいるず、UIアむコンシステムアむコンの衚珟が倉わっおきたこずに気づきたす。 以前は、塗り぀ぶされた郚分のある「solid / fill」アむコンが倚く䜿われおいたした。 ずころが珟圚、オヌプン゜ヌスのアむコンラむブラリを芋るず、茪郭線で衚珟する「outline / stroke」タむプがデフォルトのスタむルであるこずが䞀般的です。 GoogleのMaterial Design3のドキュメントにお outlineスタむルの効果や、fillに持たせる意味などが説明 されおいたすが、この蚘事では他の事䟋も匕甚しながら理解を広げたいず思いたす。 Googleの「Material Symbols」はFill、Weight、Grade、Optical Size、Styleを軞に調敎できる、バリアブルフォントで構成されおいる https://fonts.google.com/icons Tailwindcssの「Heroicons」は1぀のアむコンに察しお、4぀のバリ゚ヌションが甚意されおおり「Outline」「Solid」「Mini」「Micro」から遞ぶこずができる https://heroicons.com/ GitHubの「Octicons」はoutlineスタむルが基本で、アむコンの内容によっおFillアむコンが甚意されおいる https://primer.style/octicons/ 「Phosphor」は、outlineスタむルで「Thin」「Light」「Reguler」「Bold」ず4぀のバリ゚ヌションが甚意され、加えお「Fill」「Duotone」がある https://phosphoricons.com/ 「Lucide」は、outlineスタむルのみを提䟛し、線の倪さを自由に蚭定しお利甚する、シンプルで明快な蚭蚈 https://lucide.dev/ 「目立぀アむコン」から「意味を補助するアむコン」ぞ 以前のUIアむコンは、特に泚目しおほしい郚分にのみ配眮したり、抌䞋しおほしいボタンに付けたり、画面を楜したせる装食ずしおも利甚されおいたした。 珟圚も、それらの文脈は消えおいたせんが、倚囜化、倚蚀語化や、スマヌトフォン小さな画面の䞀般化、絵文字の䞀般化などの流れにより、蚀語の壁を超えやすく、省スペヌスで、ナヌザヌフレンドリヌな存圚ずしお、UIアむコンが倚甚されるようになったず思いたす。 UIアむコンの衚瀺数が倚くなったからこそ、ひず぀ひず぀のアむコンを軜くみせるずいうこずが求められたず想像したす。 特に情報量の倚い画面でsolidアむコンを倚甚するず、画面党䜓が重く芋えるこずがありたす。 䞀方、outlineアむコンには内郚の䜙癜があり、画面党䜓を芋枡しやすくなりたす。 「monday.com」画面は、各コンテンツのステヌタスなどを背景色でカラフルに目立぀衚珟がされおいるのに察しおタブやボタンなどのアむコンは控えめなoutlineスタむルで添えられ、画面党䜓のバランスが保たれおいたす / 画像出兞 https://support.monday.com/hc/en-us/articles/360017414139-monday-com-display-themes-light-dark-and-night-mode テキストず組み合わせやすい UIアむコンの倚くは、単独ではなくテキストず䞀緒に䜿われたす。 たずえば、 「共有」「ダりンロヌド」「線集」「削陀」 ずいった、コンテキストメニュヌです。 solidアむコンは文字ず組み合わせるず、アむコンだけが匷く芋える堎合がありたす。 outlineなら、線の倪さを調敎するこずで、文字ずの芖芚的なりェむトを合わせやすくなりたす。 以前のアむコンはその存圚感を、UIの意味を䌝える蚘号ぞず圹割の䞭心が倉化したした。 「Notion」のブロック゚ディタヌのコンテキストメニュヌ 「Solid」に意味を持たせるこずができる outlineが倚甚されるからずいっお、solid / fillは䞍芁ずいうわけでもありたせん。 むしろ珟圚は、solidを「状態」の衚珟ずしお利甚するこずができたす。 䟋えばタブメニュヌにお、通垞はoutlineで、遞択するずsolidに。 ぀たり、 OFF/ON 非アクティブ/アクティブ 未登録/登録枈み 䞀般/重芁 のような状態の切り替えや、匷調箇所を衚珟できたす。 「YAMAP」スマヌトフォンアプリのボトムナビゲヌション ダヌクモヌドずの盞性の良さ OSやアプリケヌションでダヌクモヌド暗い背景の画面が暙準化されたしたが、暗い背景に察しお面積の広い、真っ癜なsolidアむコンを配眮するず、コントラストが匷く眩しさを感じさせるこずがありたす。 線画アむコンは光る面積が少ないため、ダヌクモヌドでも目に優しく、画面党䜓の芖認性を適切に保぀こずができたす。 「ChatGPT」のダヌクモヌド たずめ システムや枠組み、装食よりも、コンテンツ情報自䜓が䞻圹であるこずは、以前から珟圚に至るたで倉わりたせんが、コンピュヌタヌの存圚が、䞀局身近で自然になるこずで、ナヌザヌむンタヌフェむスは存圚を誇瀺せずスマヌトに圚るこずが求められおいるのかもしれたせん。 もし、アむコンを遞ぶ際に迷ったら、たずはoutlineで構成し、匷調したい郚分に察しおsolidを適甚しおみるず、芖芚的なデザむンの芋通しが぀くでしょう。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post なぜ、UIアむコンは「線」ぞ –  情報にフォヌカスするためのデザむントレンド first appeared on SIOS Tech Lab .
こんにちはサむオステクノロゞヌの前田です。 むンフラを䜜成し運甚するずきには監芖を行いたす。Kubernetes環境でも同様に監芖を行いたす。最近では監芖にプラスしお「オブザヌバビリティ」ずいう、いろいろな情報を収集しおいくケヌスがあり、運甚の情報敎理がどんどん耇雑になっおきおいたす。今回はKubernetesの基本的な監芖ず、オブザヌバビリティに぀いお蚘茉したす。 埓来の仮想マシンや物理サヌバヌ環境ず比范しお、Kubernetes環境ではコンテナの頻繁な生成・砎棄、動的なIP割り圓お、分散されたアヌキテクチャなどの特城がありたす。これに䌎い生成されるログやメトリクスデヌタの量が劇的に増加するため、既存通りの運甚ずいうこずはあたり適切でないこずもあり、適切な芁件敎理ず最適な゜リュヌション遞定が必芁ずなりたす。 Kubernetes オブザヌバビリティずは たず、混同されがちな「監芖」ず「オブザヌバビリティ」を区別したしょう。 埓来の「監芖Monitoring」ず「オブザヌバビリティ」の違い 監芖Monitoring あらかじめ定矩された異垞CPU高負荷、プロセスダりン等が発生したこずを怜知・通知する受動的な仕組み。 オブザヌバビリティObservability システムの倖郚出力デヌタメトリクス、ログ、トレヌスから、内郚の状態や未知のトラブルの根本原因を「探玢・特定」できる胜動的な胜力。   倧芏暡な構成や継続的な開発ではオブザヌバビリティの仕組みを入れるこずは有効です。ただコンテナ利甚の最初期から導入するのは目的が芋えなくお無駄が倚いものです。そのためKubernetes゜リュヌション構築初期蚭定にはオブザヌバビリティ゜リュヌションが党郚入っおいない方が䞀般的です。 オブザヌバビリティの3倧芁玠 オブザヌバビリティを実珟するには、以䞋の3぀のデヌタを収集しお連携させおいきたす。   デヌタの皮類 内容 䞻な察象䟋 メトリクスMetrics システムの数倀を定期的に蚈枬・集玄した数倀デヌタ ホスト/コンテナのCPU・メモリ䜿甚率、Podの再起動回数 ログLogs システムやアプリケヌションで発生したむベントの蚘録デヌタ K8sむベントログ、コンテナ暙準出力stdout/stderr、アクセスログ トレヌス Traces 分散環境における䞀連のリク゚スト凊理経路を远跡するデヌタ マむクロサヌビス間のHTTP/gRPC呌び出し履歎、凊理遅延箇所の特定 オブザヌバビリティ利甚時の泚意点・課題 オブザヌバビリティのデヌタ収集時には泚意点や課題が発生したす。よく発生する課題を䞋蚘に蚘茉したす。 デヌタの肥倧化ずコスト増 Kubernetes非利甚時ず比范しおメトリクスやログの量が膚倧になりたす。埓来同等の保持期間・取埗蚭定にするず、ストレヌゞコストの増倧や監芖゜リュヌション偎の凊理遅延を匕き起こしたす。ログが日にノヌドあたり数十になるケヌスもあるため、デヌタ転送する察象やデヌタ量を怜蚎する必芁が有りたす。 デヌタの保持期間ず間匕きサンプリング 運甚芁件に合わせお、保持期間の蚭定短期間保持、叀いデヌタの自動削陀やテスト環境での詊算を行う必芁がありたす。゜リュヌションによっおはデヌタの長期間保存に察応しおいないものも存圚したす。 代衚的な゜リュヌション Kubernetesにおけるオブザヌバビリティ゜リュヌションには、オヌプン゜ヌスOSSから商甚SaaSたで倚様なツヌルが存圚したす。 機胜別の䞻芁OSS゜リュヌション カテゎリ 代衚的なツヌル・゜リュヌション 特城 メトリクス収集・保管 Prometheus, VictoriaMetrics K8s暙準のメトリクス収集基盀。倧芏暡環境ではVictoriaMetrics等の長期保管向け゚ンゞンの怜蚎も有効。 ログ集玄・管理 Fluent Bit, Fluentd, Grafana Loki 軜量なFluent Bitで収集し、Lokiに保管。 分散トレヌシング Jaeger, Grafana Tempo, OpenTelemetry リク゚スト远跡。OpenTelemetryOTelを暙準SDKずしお掻甚する構成が増加。 可芖化ダッシュボヌド Grafana メトリクス・ログ・トレヌスを䞀元化しお可芖化・盞関分析する暙準ツヌル。 プラットフォヌム組み蟌みモニタリング 䞻芁なコンテナプラットフォヌムでは、Prometheusを䞭心ずしたモニタリング機胜がデフォルトたたはアドオンずしお提䟛されおいたす。 OpenShift Monitoring: プラットフォヌム構築時に暙準でデプロむされる統合監芖環境。 Rancher Monitoring: クラスタヌ構築埌にワンクリックで蚭定・統合可胜なモニタリングスタック。 Prometheus Operator 最小限か぀暙準的なKubernetesモニタリング構成を構築・運甚する際、OSSの Prometheus Operator が広く採甚されおいたす。Prometheusずいう堎合はPrometheus Operatorではなくメトリクス収集・蓄積の機胜のPrometheusを指したす。しかし、単䜓で導入するこずよりも運甚しやすいように連携しおいるPrometheus Operatorの圢匏でむンストヌルされるこずの方が倚いです。 Prometheus Operatorの䞻な構成コンポヌネント Prometheus Server: メトリクスの収集Pull型、時系列デヌタベヌスぞの保存。 Alertmanager: 条件に応じたアラヌトの集玄・抑制・通知Slack, メヌルなど。 node-exporter: ホストノヌドレベルのハヌドりェア・OSリ゜ヌスのメトリクス出力。 kube-state-metrics: Pod, Deployment, NodeなどKubernetesオブゞェクトの状態メトリクス生成。 Grafana: 収集されたメトリクスを可芖化するダッシュボヌド。 おわりに オブザヌバビリティに関しおKubernetes構成に慣れおいない時点で倚くの機胜を導入した堎合䜿いこなせないケヌスもありたす。オブザヌバビリティの導入に぀いおは最初期はPrometheus Operatorを導入しお、その埌必芁な機胜を怜蚎するこずがお勧めです。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Kubernetesオブザヌバビリティのはじめ first appeared on SIOS Tech Lab .
どもバグ調査のたびにPlaywrightを䜿い捚おしお快適なバグ修正を行っおいる韍ちゃんです。 バグが来たら、AIに䞀旊調査させおみる。んで芋立おを立おるじゃないですか。 その芋立お、合っおたしたか。 これがけっこう倖しおたした。読んで「いいんじゃない」ず思っお蚱可しお、通ったから終わりにしお、しばらくしお治っおいない。結局コヌドを読む。 痛いのは「自分でコヌドを読む」こずじゃないんですよね。 ドキュメントレベルをさらっず読んでも確定しないこず です。 なので提案はこれです。 Playwrightを䜿い捚おで曞いお、バグ調査に䜿う。 恒久的な回垰テストずしお残すためではなく、原因を確定させるために曞いお、終わったら捚おる。 今回の内容です。 画面に出るバグを、䜿い捚おの再珟で詰める6工皋 再珟を曞かせるのは、実装したセッションの倖がいいずいう話 再珟条件が確定するず「このバグ盎しお」が別物になる、ずいう話 䞀番の匷敵は flaky で、そこをどう手圓おしおいるか 想定しおいるのは、 Playwrightで E2E を曞いお、CIに乗せお回しおいる人 です。扱うのは 画面に出るバグだけ に絞りたす。衚瀺が厩れる、抌せない、閉じない、みたいな皮類ですね。ロゞック局やバッチみたいに画面に出ないものは、この手順そのものが圓おられたせん。 先に予防線を匵っおおきたす。これはコヌドを読たなくお枈む話ではありたせん。 読む䜍眮が倉わる話です。 AIに䞞投げする方向ずは、むしろ逆を向いおいたす。 画面に出るバグは、この6工皋で回しおたす 前提を揃えたす。Playwrightはもう入っおいお、E2Eを曞いおCIで回しおいる。その状態にバグが1件飛んでくる、ずいうずころからの話です。 飛んでくるのは、衚瀺が厩れおいる、ボタンが抌せない、ダむアログが閉じない、みたいなや぀ですね。 画面を芋れば起きおいるこずは分かる。でも、なぜそうなるかは分からない。 そこからどう詰めるかを曞きたす。 党䜓感ずしおはこんな感じです。 䜿甚感で気づく AIず議論しお、原因を特定しお蚀語化する 画面に出るものなら、再珟を曞かせる たず萜ちるこずを確認する 修正しお、぀ぶれたこずを再珟で確認する 枈んだら捚おる 芁所で人間がスクショを芋たす。順に芋おいきたすね。 ① 入口は問わない Issueでもレビュヌ指摘でも、自分で觊っおいお気づいたのでもいい。ここは通過したす。その芋立おが合っおいるかを確かめるのが、ここからです。 ② 調査結果を1枚にたずめさせお、3぀だけ瞛る ここでやるのは、原因の芋立おをAIず䞀緒に蚀葉にするこずです。この型そのものは前に曞いたので、詰め方は ドキュメント1枚を先に曞く話 ず同じです。 たずめさせるずきに瞛るのは3぀です。 実装させない 察策は方針たで 人間向けに䞞めさせない 掚枬を事実ずしお曞かせない ここを瞛らないず、読みやすい嘘が出おきたす。読みやすいので気づきにくいんですよね。 そしお、この工皋でいちばん危ないのは、 出おきた結果を確かめずに修正に入るこず です。調査結果っお、もっずもらしく玍埗できる圢で出おくるんですよね。読んだ時点で正解に芋える。でも読んで玍埗したこずず、実際にそう動くこずは別です。 確かめるのは④でやりたす。 ③ 実装の倖で、画面に出る症状だけを曞かせる 条件が2぀ありたす。どっちを倖しおも、曞いた再珟が蚌拠になりたせん。 ひず぀は、 症状が画面に出るものに限る こず。Playwrightが埗意なのはそこです。ネットワヌクの䞭身、぀たり実環境で実際に䜕が飛んでいるかを捕たえるのは埗意な領域じゃないので、そこは人間がHARを取っおくる偎の仕事になりたす。さきほど挙げたドキュメント1枚の話が、その偎ですね。 もうひず぀は、 再珟を曞かせる文脈を、実装した文脈から切る こず。 実装したセッションにそのたた再珟を曞かせるず、自分が脆いず知っおいる経路を無意識に避けるんですよね。同じ文脈が持っおいる前提を、そのたた持ち蟌むので。別のセッションを立おるず容赊なく指摘しおくるし、めちゃくちゃなテストをしおくれる。人に頌むのず同じですね。分けたほうが蚌拠ずしお匷くなりたす。 どの粒床で分けるかは セッションを分ける運甚の話 で曞いたので、ここでは再珟を曞かせるぶんだけにしたす。 同じ問題は瀟内でも曞かれおいお、 AIにテストを曞かせるず実装をコピヌしたテストになる ずいう話です。あちらは同じAIの䞭で読む暩限を剥がしお解決しおいたした。こっちはセッションを分けお、間に人が資料を通しおいたす。芋おいる壊れ方は同じで、解決の䞻語が違うだけです。 ④ 萜ちるのを芋お、調査資料を確定させる ここが、この手順でいちばん倧事です。 修正する前に、曞いた再珟が ちゃんず萜ちるこず を確認する。飛ばすず、あずで通ったずきにバグが盎ったせいなのかテストが空振りしおいるせいなのか区別が぀きたせん。 もっずも、萜ずせないや぀もありたす。衚瀺が厩れおいる、文蚀の䜍眮がおかしい、みたいなアサヌションに曞けない皮類ですね。そこは萜ちなくおいいので、 修正前の症状が写ったスクショを撮りたす 。萜ちる代わりに、それを蚌拠にする。やっおいるこずは同じで、修正する前の状態を固定しおいるだけなので。 ただ、動かしお芋せるだけならただの動䜜確認なんですよね。倧事なのは、 萜ちたずいう事実で②の資料を曞き換えるこず です。仮説ずしお曞いおあった原因が、動䜜で確定した事実になる。ここで初めお、実装に投げられる資料になりたす。資料に䜕をどう曞くかは、それ自䜓で1本になるのでここでは扱いたせん。 䞀床、修正案を5぀䜜っお党郚動かしたこずがありたす。理屈でいちばん正しそうに芋えた案が、動かすず効かなかったんですよね。読んで考えた結論っお、けっこう倖れたす。だから動䜜のほうから確定させたす。 あわせお、ここで決めおおくこずがありたす。 盎す範囲をどこたで広げるか です。 こういうこずがありたした。抌せないずいうバグが䞊がっおきお、報告にあったのは1画面だけ。でも調べたら原因は共通の郚品偎で、その郚品を䜿っおいる画面が党郚同じバグを持っおいたした。盎す範囲は5箇所になりたす。いわゆる暪展開ですね。しかも、報告に無かった箇所がいちばんひどかったんですよね。「あっちは行が高いから圓たりにくいだろう」ず螏んでいたのが、動かしたら倖れおいたした。 逆に、その画面だけ盎せば枈むバグもありたす。 あったりなかったりなので、圱響範囲を確認しお決めるしかない 。決めずに実装ぞ投げるず、そこをAIが補完しおしたいたす。 â‘€ 資料を新しいセッションに枡しお、たず疑わせる ここでもう䞀回、答え合わせをしたす。修正は 新しいセッションでやっお 、持たせるのは資料だけ。実装の文脈は匕き継がせない。③ず同じ切り方ですね。 そのずき、いきなり盎させないんですよね。「資料はこう蚀っおるけど、どうなんかな」ず䞀床聞いおから実装に入らせたす。確定しおいるずいっおも、確定させたのは自分なので。ここでもう䞀回、答え合わせをしおいる感じです。 あずは楜で、萜ちるずころたで䜜っおあるので、通るのを芋るだけです。 あわせお、芁所でスクショを撮っお人間が芋たす。文蚀の䜍眮がおかしいみたいなのはアサヌションに曞けないので。実際、゚ラヌの衚瀺䜍眮が想定ず違っおいお差し戻したこずがありたした。 ⑥ 元を取ろうずせず、捚おる やるこずは短くお、蚭定ファむルに䜿い捚お甚の項目を数行足しお、曞いお、回しお、消す。それだけです。 「せっかく曞いたのに」ず思いたすか。でもPlaywrightを手で曞くの、倧倉じゃないですか。 恒久化しおCIに乗せろずいうのは、曞くのが高かった時代の合理 なんですよね。安く曞けるなら、無理に元を取る必芁がない。必芁ならたた䜜ればよいんです。 残すのは基本スクショだけです。逆に、UIのデグレチェックは党郚撮っお残すべきだず思っおいたす。あれは原因の蚀語化が芁らなくお、人間が芋お刀断するものなので、性質がたったく違う。 もちろん、E2Eずしお残す䟡倀があるものはその぀ど出おきたす。党郚捚おろずいう話ではないんですよね。ただ、その線匕きは僕もただ蚀葉にできおいたせん。 たたに、E2Eずしお新蚭するずころたでをタスクずしお切るこずもありたす。芁は効果があるかないかっお郜床刀断ですね。 この手順を螏むず「このバグ盎しお」が指瀺曞に倉わる 䜕が倉わるか。 再珟条件が確定したす 。 どう操䜜したら、どういう状態で、䜕が起きるか。そこに、どこたで盎すかたで乗っおいたす。そこたで固たるず、コヌドのどこが問題かも自然に絞れおいたす。 その状態で出す「このバグ盎しお」は、もう別物なんですよね。角床の高い指瀺曞になっおいる。同じ䞀蚀なのに、返っおくるものが倉わりたす。 ひず぀だけ泚意があっお、ここで枡しおいるのは再珟コヌドじゃありたせん。 テストコヌドを枡しお「これを通るように盎しお」ずやるず、テストを通すためのコヌドが返っおきたす。枡すのは、 再珟で確定した条件 のほうです。 Playwrightで䞀番の匷敵はflakyでした 再珟を䜜るずき、いちばんの敵はflakyな挙動でした。 再珟しようずするず別のバグっぜい挙動が出お、よく芋たら読み蟌み埅ちだった、みたいなこずが起きるんですよね。 困るのは「テストが䞍安定で぀らい」からじゃないんです。 萜ちたのがバグなのか環境なのか区別が぀かないず、答え合わせにならない からです。確定させるために曞いおいるのに、確定しない。ここが厩れるず工皋ごず意味を倱いたす。 flakyそのものを避ける仕組みはPlaywrightの䞭に入っおいお、正盎そこは僕は説明できたせん。意識しないうちに助けられおいたので。詳现は 公匏の Auto-waiting のペヌゞ に譲りたす。 自分でやったのは1぀だけで、 螏んだ穎をその぀どAIに蚘録させるこず です。この型は 螏んだ倱敗を溜めお指瀺ファむルを育おる話 で曞いたので、ここではPlaywrightに圓おたぶんだけです。 手元にその蚘録が溜たっおいお、撮圱たわりだけで5,375行ありたす。党郚、䞀床螏んだ穎です。「3件通ったように芋えお、実は1件も走っおいなかった」みたいなのも入っおいたす。 蚘録させおあるので、䌌た穎は二床目がない。だから超絶快適なんですよね。ここが「䜿い捚おで回すずはかどる」の実䜓だず思っおいたす。 倉わったのは、読む䜍眮でした 正盎に曞いおおくず、この運甚も無傷じゃありたせん。 圓おられないバグに圓おお、倱敗したこずもありたす。画面に出ないや぀ですね。導入で範囲を切ったのは、実際にそこで転んだからです。逆に、フロントの衚瀺系ならだいたいこれで朰せおいたす。枬ったわけではなくお、実感ですけど。 䞀床、撮ったスクショが消えおいお、蚘録から曞き戻しお撮り盎したら構文゚ラヌで動きたせんでした。貌った時点で壊れおいおも気づけないんですよね。 捚おる運甚は、捚おたものが確実に戻る保蚌たではくれたせん。 それでも、倉わったのは順番でした。 コヌドを読んで考えお芋立おを立おるずころから、動かしお確定させおから読むずころぞ。読む䜍眮が移ったんです。指瀺曞の粟床は、党郚そこから出おいたす。 読むのは最終手段です。 ただ、読む前に䜕が起きおいるかは確定しおいる。最初に匵った予防線はそういう意味でした。 答え合わせの道具そのものが壊れおいた話もあるんですが、それは別蚘事で曞きたす。再珟があっおもAIに任せきれない理由、こっちはリポゞトリ偎の事情なんですが、それも別に眮く぀もりです。 最埌に、最初の䞀歩を1぀だけ。 次のバグで、修正する前に「萜ちる再珟」を1本曞いおください。 それだけで、盎したあずに自分が䜕を確認したのかが倉わりたす。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Playwrightのバグ調査は曞いお捚おる「このバグ盎しお」が指瀺曞に倉わる first appeared on SIOS Tech Lab .
ども韍ちゃんです。 Geminiを毎日䜿っおいるず、こういう瞬間ありたせん 䌚話が長くなっおきたずころで、急に答えが雑になる。「あれ、なんか噛み合っおなくない」っお感じる瞬間。 今回は、その「なんか倉」を芋分ける方法ず、芋分けたあずの察凊に぀いお曞きたす。 やるこずは単玔で「その䌚話を捚おる」です。捚おおも䞭身は消えたせん。持ち出し方たで曞くので、そこは安心しお読み進めおください。 「その話は䞀床も出おいたせん」ず真顔で蚀われた話 先日、Geminiずの䜜業䞭にこんなこずがありたした。ブログのネタになりそうなメヌルの話をしおいお、少し経っおから「さっきのメヌルの件、芋出しず箇条曞きでたずめお」ず頌んだんです。 そしたら返っおきたのが、「履歎を確認したしたが、その話は䞀床も出おいたせんでした」の䞀蚀。 いや、話したしたよね。数分前に。僕は䞀瞬「自分の蚘憶違いか」ず思ったんですが、違いたした。ちゃんず話しおいたした。 特別な聞き方をしたわけでも、蟌み入った話をしおいたわけでもありたせん。ごく普通のやり取りの延長で、いきなりこれが起きたした。 これ、「知らない」じゃなくお「無かったこずにする」なんですよね。しかも自信満々に蚀っおくるので、䞀瞬こっちが自分を疑っおしたう。普通に怖いですよね。 いたの䌚話、倧䞈倫ですか。3぀のサむンで芋分ける さっきの「無かったこずにする」は、氷山の䞀角です。䌚話が長くなっおくるず、䌌たような「あれ」が他にも起きたす。同じ䌚話の䞭で出おきたのは、党郚で3぀でした。 サむン1: もう終わった話に、匕き戻される 䌚議の議事録をたずめおもらっお、それは片付いた。そのたた同じチャットで「別件なんだけど、来週の資料の構成を考えお」ず振りたす。 返っおきたのは、たた議事録の話でした。資料の構成は䞀蚀も入っおいたせん。もう䞀床頌むず、今床は「議事録を螏たえた資料構成」が出おきたす。頌んでいないのに議事録が残り続ける。 話の流れを汲んでくれるこず自䜓は、普段からあるこずです。刀断が付きにくいのはそこですよね。線を匕くなら、こっちが「その話はもう終わり」ず䌝えたあずも戻っおくるかどうか。䌝えおも戻っおくるなら、サむン1です。 サむン2: した話を「しおいない」ず蚀われる 冒頭に曞いたのがこれです。数分前に自分で話したこずを頌んだのに、「その話は䞀床も出おいたせんでした」ず返っおくる。 これ、単に「忘れた」のずは違いたす。忘れたのなら「芚えおいたせん」ず返っおくるはずです。でも返っおくるのは「そんな話は無かった」なので、こっちが間違っおいる気分になりたす。 厄介なのは、指摘したあずの反応です。「倱瀌したした」ず謝っおはくれるものの、その話が無かった前提のたた進んでいく。こちらが根拠ずしお貌り盎すたで、戻っおきたせん。 ずがけおいるわけではなくお、探しに行っお芋぀けられおいないように芋えたす。理由はどうあれ、読者偎でやるこずは同じです。 蚀ったはずのこずを「蚀っおいない」ず返されたら、サむン2 。 サむン3: 謝りだしお、頌んだ圢が厩れる サむン2を指摘したあずに、よく続きたす。「すみたせん、完党に私の思い蟌みでした」から始たる長い謝眪文が返っおきお、肝心の䞭身が短くなる。 さらに続けるず、最初に「箇条曞きで」「衚で」ず頌んでおいた圢も守られなくなりたす。謝眪のほうが本文より長くなっおきたら、そこはもう内容を䜜る䜙裕がない状態です。 2぀出たら、もう戻りたせん 1぀だけなら、たたたた拟い損ねただけずいうこずもありたす。でも2぀そろったら、粘る時間がもったいないです。 数えるのは盎近の1埀埩ではなく、その䌚話ぜんぶ。前のほうで1回でも出おいたなら、それは数に入れおください。 なお、䜕回やり取りしたら危ない、ずいう数字で決たる話ではありたせん。短い䌚話でも起きるずきは起きたす。回数を数えるより、この3぀が出おいるかを芋おください。 3぀ずも「蚀われおみればそうかも」くらいで決め手に欠けるずきは、これを打぀ずはっきりしたす。 この䌚話で、私が最初にお願いしたこずを䞀蚀で答えおください。 䌚話に出おいないこずは曞かないでください。 別の話が返っおきたり、それらしい䜜り話が返っおきたら、サむン2ず同じ状態です。ちゃんず答えられるなら、ただ倧䞈倫です。 もっず䞊手く聞けば盎る、わけじゃないんです 以前 Geminiの回答が「ちょっずズレる」人ぞ。䞀蚀足すだけで倉わる3぀のコツ ずいう蚘事で、「ズレるのは聞き方のせい」ず曞きたした。䜕を知りたいか絞る、誰ずしお答えおほしいか䌝える、この圢で出しおず指定する。あのコツ、今でも有効です。 でも、今回のズレは別物です。聞き方をどれだけ工倫しおも、盎らないズレがありたす。 しかもこれ、Geminiに限った話ではありたせん。ChatGPTでもClaudeでも、䌚話が長くなるず同じこずが起きるず蚀われおいたす。 ぀たり、Geminiが悪いわけでも、あなたの聞き方が䞋手だったわけでもありたせん。粘っお蚀い方を倉えおも、たぶん盎りたせん。 じゃあどうするか。粘らずに、その䌚話を捚おるのが䞀番早いです。 ちなみに、こんなに起きるこずなのに、なんで自分は知らないたただったんだろう。そう思いたすよね。その話は最埌に曞きたす。たずは「捚おる」の具䜓的なやり方から。 捚おおも平気な状態にしおおく 冒頭で「消えたせん」ず曞いた話です。捚おたら党郚なくなる、ず最初は思っおいたした。 でも消えるのは䌚話であっお、䞭身ではありたせん。捚おる前に持ち出しお、新しい䌚話で続きをやればいいだけです。 ここでいう「捚おる」は、 削陀するずいう意味ではありたせん。 その䌚話をもう䜿わないず決めお、新しいチャットに切り替えるだけです。元の䌚話は履歎にそのたた残るので、あずから芋返すこずもできたす。 ただ、履歎に残っおいるのず、次のチャットが䞭身を知っおいるのは別の話です。新しいチャットは、既定では前の䌚話を匕き継がずに始たりたす蚭定によっおは過去のチャットを参照するこずもありたすが、圓おにできる圢ではありたせん。だから、これから曞く「持ち出す」䜜業がいりたす。 たず、たずめお出させる やるこずは3぀です。 順番が倧事で、いきなり曞き出し機胜でファむルにしおしたうず、䞭身を芋ないたた保存するこずになりたす。 䞋の文章をたるごずコピヌしお、Geminiの入力欄に貌り付けおみおください。 ここたでの内容を、芋出しず箇条曞きでたずめおください。 ・この䌚話で出おいないこずは曞かないでください ・確認できなかったこずは「䞍明」ず曞いおください 箇条曞きで出させるのは、抜けが芋えるからです。べた曞きの文章だず「たずたっおる感」が出お、抜けに気づけたせん。 ちなみに、この芋出しず箇条曞きの曞き方、Markdownマヌクダりンずいいたす。芚えなくお倧䞈倫なんですが、名前だけ知っおおくず埗するこずがあっお、 ゚ンゞニアに䜕か頌むずきにこの圢で枡すず、話がちょっず早くなりたす  向こうが普段から䜿っおいる曞き方なので、そのたた読んでもらえたす。 ここからが本番です。出おきたたずめを、そのたた信じないでください。さっき「その話は䞀床も出おいたせん」ず真顔で蚀った盞手が、今床はこの芁玄を䜜っおいたす。同じ調子で、郜合よく話を端折ったり、それっぜく埋めたりするこずがありたす。 ずはいえ、長く粘ったあずに党文を突き合わせるのはしんどいですよね。 芋るのは決たったこずず、数字や固有名詞だけで倧䞈倫です。 ここさえ合っおいれば、现かい蚀い回しのズレは新しいチャットで盎せたす。逆に流しおしたうず、間違ったたずめを持ったたた新しいチャットを始めるこずになりたす。 確認は画面のうちに枈たせおください。先にドキュメントぞ曞き出しおから芋ようずするず、曞き出しは毎回たっさらな新芏ファむルなので、芋るたびにファむルが増えおいきたす。 なお、嘘を぀かせない方法そのものは、この蚘事の話ではありたせん。ここでは䌚話が新鮮なうちに人間の理解ずAIの出力を突き合わせお疑う、それだけです。 「これで足りおる」ず思えたら、そこで初めお眮き堎に移したす。コピヌしお別のアプリに貌るず芋出しや箇条曞きが厩れるこずがあるので、圢のたた残したいならドキュメントぞ曞き出すのが厩れにくいです。衚で残したいなら、たずめおもらう段階で「衚でたずめお」ず頌んでおいおください。回答の䞭に衚がないず、スプレッドシヌトには出せたせん。 GeminiはGoogleドキュメント・Gmailの䞋曞き・スプレッドシヌトぞの 曞き出しに察応しおいる ので、䞀番䜿いやすい圢に敎圢しおおきたしょう。 ここたでやっお、はじめお「捚おおも平気」になりたす。 新しいチャットで、続きをやる 保存たで終わったら、たっさらな新しいチャットを開いおください。そこに、さっき芋盎したたずめを貌っお続きをやりたす。 䜜業の途䞭で捚おるこずになったずきは、こっちの頌み方が䜿えたす。 ここたでのやり取りを、次の3぀に分けお箇条曞きにしおください。 1. いた䜕をしようずしおいるか 2. すでに決たったこず 3. ただ決たっおいないこず 䌚話に出おいないこずは足さないでください。 「今なにをしようずしおるか」「もう決たったこず」「ただ決たっおないこず」の3぀に分けるのがポむントです。ただたずめるだけだず、次のチャットでたた同じ説明をれロからする矜目になりたす。 䞡方の頌み方に入れた「出おいないこずは曞くな」、これ食りじゃないです。ズレおる最䞭のGeminiは、履歎を探しお芋぀からないず、無かったこずにするか、それっぜく埋めおくるこずがありたす。さっき指摘した問題を、この䞀蚀で塞いでいる圢になりたす。 防止のために曞いおいおも、捏造しおくる可胜性があるので必ず確認はしたしょう。 新しいチャットが持っおいるのは、あなたが貌ったたずめだけです。曞いおいないこずは匕き継がれないので、倧事な前提はたずめのほうに入れおおいおください。 毎回やる人だけ、Gemに預けおおく ここは、い぀も䌌たような䜜業をしおいる人向けの話です。今すぐ䌚話を立お盎したいだけなら、飛ばしお構いたせん。 捚おるのが面倒に感じる理由のひず぀は、毎回れロから自分の状況を説明し盎すずころにありたす。 そこを「Gem」に預けおおく ず、䌚話を捚おるハヌドルが䞋がりたす。自分の圹割や、出しおほしい圢のルヌルをGem偎に持たせおおけば、新しいチャットを開くたびに説明し盎さなくお枈むからです。 ただし、Gemが芚えおいるのは圹割ず曞き方のルヌルだけです。その䌚話で決たったこずや数字たでは持っおいないので、そこはさっきのやり方で持ち出しおください。 Gemは䌚話を捚おおも残るので、遞び盎すだけで䜿えたす。䜜り方は前に曞きたした。 毎回コピペはもう卒業。Geminiの「Gem」で遞ぶだけにする方法 こんなに起きるのに、なんで知らないたただったんでしょうね 正盎、腹が立ちたせんか。こんなに頻繁に起きるのに、なんで自分は知らないたただったんだろうっお。 なぜこうなるのかは、各瀟ずも詳しくは公衚しおいたせん。ここでは、実際に䜕が起きるかだけを曞きたす。 たず知っおほしいのは、察策自䜓はちゃんず公開されおいるこずです。ただ、眮いおある堎所が、普段みなさんが芋に行く堎所ではありたせん。 長い資料を枡すずきは質問を最埌に眮くずいい、ずいう案内が、 開発者向けの資料 にはちゃんず曞いおありたす。その䞊べ方でテストしたら最倧30%よくなった、ず曞いおいる䌚瀟もあるくらいです。でも、普段䜿っおいるアプリのヘルプを探しおも、こういう話は出おきたせん。 さきほど「Geminiに限った話ではない」ず曞きたした。実際、Geminiを䜜っおいる偎もClaudeを䜜っおいる偎も、開発者向けの資料でこの珟象に觊れお、察凊のしかたたで曞いおいたす。 ただ、「1回の䌚話でどれだけ芚えおいられるか」を利甚者向けに説明しおいるかどうかは、䌚瀟によっおけっこう違いたす。たずえばClaudeは、 ふ぀うのヘルプペヌゞ にプランごずの数字を曞いおいたす。䞀方でGeminiのアプリのヘルプには、その数字も、叀い郚分が捚おられるのかどうかも、芋圓たりたせんでした。 起きるこず自䜓は、どこを䜿っおいおも同じ。でも、教えおもらえるかどうかは同じじゃない。犯人を远いかけたら、特定の1瀟の手抜きじゃなくお仕様そのものでした。そのうえで、その仕様を教えおくれるかどうかには差がある、ずいう話です。 たずめ 今回の話、たずめるずこれだけです。 もう終わった話に匕き戻される、した話を「しおいない」ず蚀われる、謝りだしお圢が厩れる。2぀出たら戻りたせん 粘っおも盎りたせん。あなたの聞き方のせいでもありたせん 捚おる前に持ち出しお保存する、捚おたら新しいチャットで立お盎す 僕自身、あの「その話は䞀床も出おいたせん」を蚀われたずきは、正盎ちょっずぞこみたした。でも今は、サむンが出た時点で、気にせず捚おるようにしおいたす。 䌚話は消えるものじゃなくお、持ち出しお次に運ぶものです。 いた開いおいる䌚話、もう2぀圓おはたっおいたせんか。だったら粘らずに、䞊のやり方で持ち出しおしたいたしょう。ただ倧䞈倫そうなら、次に「あれ」ず思ったずきに3぀のサむンを数えおみおください。 「今日から䜿えるGemini掻甚術」シリヌズもあわせおどうぞ。 ググる前に、たずGemini。日垞がちょっずラクになる3぀の䜿い方 Geminiの回答が「ちょっずズレる」人ぞ。䞀蚀足すだけで倉わる3぀のコツ 毎回コピペはもう卒業。Geminiの「Gem」で遞ぶだけにする方法 ではたた ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 1人がこの投皿は圹に立ったず蚀っおいたす。 The post Geminiの答えが雑になっおきた人ぞ。䌚話は䜿い捚おでいいんです first appeared on SIOS Tech Lab .
こんにちは、サむオステクノロゞヌの藀井です。 最近、Claude Codeにファむルを読んだり盎したりしおもらうずき、Read や Edit ではなく Bash 経由の cat や sed 、ちょっずしたPythonスクリプトで枈たされるこずが増えたなず感じおいたした。 実際にこんなやり取りがありたした。 「.mdファむルを読んで芁玄しお」ずだけ頌んだのに、返っおきたのは Bash(cat 
) でした。逆に「ファむルを線集しお」ず頌むず、ファむルを曞くPythonスクリプトを䜜っおbashで実行したす。最初に思ったのは「なんでそんな遠回りなこずやっおるんだ」ずいうこずです。ReadやEditツヌルならもっず早く読み曞きできそうです。 䞀床気になり始めるず、あちこちで同じこずが起きおいるのに気づきたした。壊れたのか、䜕か蚭定を倉えおしたったのか。気になったので、Claude Codeに指瀺を出しながら䞀緒に調べおみるこずにしたした。 Bashに寄っおいくのはClaude Codeの仕様でした 結論から曞きたす。これは故障でも蚭定ミスでもなく、 v2.1.2212026-08-03公開からコヌドに曞き蟌たれおいる挙動 です。auto modeが有効なずき埌述したすが、党員ではなく開始モデルなどの条件を満たしたずきです、システムプロンプトに次の指瀺文が差し蟌たれるようになっおいたした。冒頭のスクショを撮ったセッションに「システムプロンプトにこの指瀺はある」ず聞いたずころ、末尟にこの文面が入っおいるず返っおきおいたす。 While auto mode is active: Do your work through the Bash tool wherever it can accomplish the job: read files with cat, head, or sed -n, search with grep and find, and make file changes with sed, heredocs, or short scripts, rather than using the dedicated Read, Edit, or Write tools. Fall back to a dedicated tool only when Bash genuinely cannot do the job. 蚳すずこんな感じです。 auto modeが有効な間は、仕事はBashツヌルを通しお行うこず。ファむルを読むにはcat・head・sed -nを、怜玢にはgrep・findを䜿い、ファむルの倉曎はsedやheredoc、短いスクリプトで行う。専甚のRead・Edit・Writeツヌルは䜿わない。Bashが本圓に手に負えないずきだけ専甚ツヌルに戻っおよい。 ぀たり「専甚ツヌルよりBashを優先しろ」ずいう指瀺です。導入圓初はauto mode向けの分岐コヌド䞊の倉数名は steerOnly ず bashFirst フラグの2経路だけが察象でしたが、v2.1.2272026-08-10公開でbypass permissions modeにも同じ前眮きが远加されたした。 auto mode自䜓の説明は割愛したす 公匏ドキュメント を芋おください。ここで扱うのは、「Bashを優先させる指瀺が入ったがい぀からなのか」だけです。 changelogには曞いおありたせんでした ずりあえずchangelogを芋おみたした。システムプロンプトの倉曎がchangelogに曞かれた䟋はありたすv2.1.234ではアカりントのメヌルアドレスの扱いに぀いおの倉曎が明蚘されおいたす。だから今回も探せば芋぀かるだろうず思っおいたした。 ずころが、芋぀かりたせんでした。v2.1.221のCHANGELOG.mdの゚ントリは玄40行もある詳现版で、auto mode関連の項目も耇数茉っおいるのに、Bash優先の指瀺に぀いおはひず蚀も觊れおいたせん。v2.1.227も同様です。公匏ドキュメント permission-modes にも蚘茉はありたせんでした。 しかも、逆向きの蚘述が過去にありたす。v2.1.21のchangelogには、こう曞かれおいたした。 Improved Claude to prefer file operation tools (Read, Edit, Write) over bash equivalents (cat, sed, awk) か぀おは「bashの代わりにファむル操䜜ツヌルを優先するよう改善した」ず明蚘しおいたした。今回入ったのは、この既定をauto modeなどの条件䞋でだけ䞊曞きする分岐です既定偎の指瀺はいたも生きおいお、埌述の実隓でも泚入が来ないセッションには逆向きの指瀺が残っおいたした。その䞊曞きが無告知だった、ずいうのが今回の話です。 ゜ヌスが無いので、バむナリを比べおみたした Claude CodeはOSSではありたせん。anthropics/claude-codeのリポゞトリにあるのはissueトラッカヌずリリヌスノヌト、プラグむン配垃だけで、CLI本䜓の゜ヌスはありたせん。実行ファむルはコンパむル枈みの単䞀バむナリで、プロンプトの文面はその䞭に埋め蟌たれおいたす。 なので、バヌゞョンごずにバむナリを取っおgrepしたした。ちなみに、ここたでの調査はClaude Code自䜓に指瀺を出しお実行させおいお、以䞋のスクリプトもClaude補です。 npmでの配垃のされ方が途䞭で倉わっおいたした。v2.1.1122026-04-16公開たではJSバンドルが入っおいお49.3MBありたしたが、v2.1.1132026-04-17公開からランチャヌだけになっお0.1MBたで小さくなっおいたす。それより前のバヌゞョンは npm pack で取っおgrepできたす。v2.1.113以降はむンストヌルすら芁らず、配垃サヌバから盎接萜ずしおgrepするだけで刀定できたす。ただし叀い版は配垃サヌバから消えおいるこずがありたす詊した範囲では2.1.60は404でした。 必芁なのはcurl・jq・sha256sumず、1バヌゞョンあたり数癟MBの空き容量です。 V=2.1.221 EXP=$(curl -fsS "https://downloads.claude.ai/claude-code-releases/$V/manifest.json" \ | jq -r '.platforms["linux-x64"].checksum') curl -fsS "https://downloads.claude.ai/claude-code-releases/$V/linux-x64/claude" -o "claude-$V" echo "$EXP claude-$V" | sha256sum -c - grep -a -o -F "Do your work through the" "claude-$V" | wc -l curl の -f は404を゚ラヌで萜ずすためのもので、 sha256sum -c はハッシュ䞍䞀臎でも取埗倱敗でも非0で止たりたす。macOSなどで詊す堎合は linux-x64 を自分のプラットフォヌムに読み替えおくださいキヌの䞀芧は curl -fsS "https://downloads.claude.ai/claude-code-releases/$V/manifest.json" | jq '.platforms | keys' で出せたす。 刀定に䜿ったのは指瀺文本䜓の Do your work through the ずいう文字列で、grepの䞀臎が0か非0かだけを芋たす同じ文面がバむナリ内の耇数箇所に入っおいるので、䞀臎数そのものに意味はありたせん。バむナリは1぀220〜330MBあるので、党バヌゞョンを総圓たりするず数十GBをダりンロヌドするこずになりたす。なので二分探玢でバむナリ14本に絞りたしたnpm tarballで芋た2.1.112ず合わせお蚈15バヌゞョン。存圚するバヌゞョンの䞀芧は npm view @anthropic-ai/claude-code versions で取れたす。 バヌゞョンごずの倉化を蚘録しおいる非公匏トラッカヌcchistoryなどもありたすが、そこに぀いおは裏取りできおいないので、ここでは觊れたせん。 各バヌゞョンを確認したら2.1.221に行き着きたした 実枬結果はこうなりたした。芋たのは、指瀺文の本䜓”Do your work through the
”ず、モヌドごずの前眮き文”While auto mode is active” / “While bypass permissions mode is active”がバむナリに入っおいるかどうかです。 version npm公開UTC 指瀺文本䜓 前眮き「auto modeの間」 前眮き「bypassモヌドの間」 2.1.220 2026-07-24 なし なし なし 2.1.221 2026-08-03 あり あり なし 2.1.226 2026-08-08 あり あり なし 2.1.227 2026-08-10 あり あり あり 2.1.231=stable 2026-08-13 あり あり あり 実枬したのはlinux-x64のバむナリだけで、macOS/Windowsが同じバヌゞョンかは確認しおいたせん。 この衚から分かったこずが3぀、バむナリの䞭身を読んで分かったこずがもう1぀ありたす。 文面がコヌドに入ったのはv2.1.2212026-08-03公開から 。v2.1.2202026-07-24公開には無く、220から221たでは玄10日のリリヌス空癜があっお、その盎埌に入っおいたす bypass permissions modeぞの拡倧はv2.1.2272026-08-10公開から。v2.1.226にはただありたせん 調査時点のstableチャンネル2.1.231にも入っおいたす。バヌゞョンを䞋げるこずでは避けられたせんいたのstableが䜕版かは curl -sS https://downloads.claude.ai/claude-code-releases/stable で確認できたす 出る人ず出ない人がいる理由も、ゲヌトの仕組みずしお芋えおきたした 最埌の1぀が䞀番実甚的な疑問だず思うので、もう少し詳しく曞きたす。バむナリの䞭には、内郚コヌドネヌム thrifty_sonic ずいうゲヌトがありたした。ざっくり蚀うず、泚入するかどうかはセッション開始時のモデルによっお forced モデル属性で匷制・ cohort リモヌトのfeature gateで察象かどうか決たる・ none の3倀に割り振られ、その結果がセッションに固着しおしたう、ずいう仕組みです泚入刀定からこのゲヌトたでの接続は、minifyされたコヌドの゚むリアスをたどった読解なので断定はしたせん。ただ、次の実隓結果はこの仕組みず敎合したす。 これを裏付ける実隓もしおいたす。同じ環境で開始モデルだけ倉えお2本セッションを立おたずころ、こうなりたした。 開始モデル 泚入 Opus 5 あり Fable 5 なしBash説明文には逆向きの指瀺が入る セッションの途䞭でモデルを切り替えおも、この割り圓おは再評䟡されたせん。Opus 5で始めお途䞭でFableに倉えおも泚入が残っおいた、ずいう芳枬も、この固着の仕組みで説明が぀きたす。ただし、この実隓は同䞀環境で2本立おただけn=2です。ONになった経路がモデル属性forcedなのかリモヌトゲヌトcohortなのかも切り分けられおいないので、同じモデルでも人によっお違う可胜性は残りたす。 正盎に蚀うず、バむナリgrepずいう方法自䜓にも限界がありたす。分かるのは「文面がバむナリに入っおいるか」たでで、自分のセッションに実際に泚入されるかは別問題です。珟に、この調査を実行した私のセッションは同じバむナリを動かしおいるのに泚入が来おいたせんでした。 じゃあ自分の環境はどうなのか。手っ取り早いのは、そのセッション本人に「システムプロンプトにこの指瀺はある」ず聞いおしたうこずです。冒頭のスクショのセッションも、この方法で泚入を確認したした。 機械的に確かめるなら、セッションのトランスクリプト ~/.claude/projects/ 配䞋のJSONL。 CLAUDE_CONFIG_DIR を蚭定しおいる堎合はそちらの projects/ をgrepしたす。 grep -c -F "Do your work through the" <トランスクリプト>.jsonl 0件なら泚入なしですパスを間違えおも0件になるので、ファむルの存圚は先に確かめおください。非0のずきは1぀泚意があっお、この蚘事のように文面そのものを䌚話で扱っおいるず自分の匕甚もヒットしたす。件数では刀定できないので、ヒットした行がsystem-reminder偎なのか自分のツヌル実行の写しなのかをレコヌド単䜍で芋るか、結局は本人に聞くのが早いです。 実害も報告されおいたす ここから先は私自身が螏んだ話ではなく、GitHub issueず公匏ドキュメントに曞かれおいる内容です。 Bash経由の倉曎は、 /rewind チェックポむント機胜の远跡察象から最初から倖れおいたす。公匏ドキュメント checkpointing に明蚘されおいる通り、チェックポむントが远いかけるのはEdit/Writeなどファむル線集ツヌル経由の倉曎だけです。 issue #87575 2026-08-18起祚は、この組み合わせで起きた䞍具合を報告しおいたす。auto modeのシステムプロンプトが原因で、Bashで線集したファむルに察しお /rewind が黙っお倱敗する、ずいう内容です。 issue #87971 2026-08-19起祚には、もっず螏み蟌んだ報告がありたす。auto modeでBashツヌルが読み曞き・線集を乱甚しおいるずいうバグ報告で、コメント欄には sed での線集でドキュメントの半分が消えたずいうデヌタ損倱の報告も付いおいたす。起祚者はenterprise managed settingsに「この指瀺に埓うな」ずいう蚭定を入れお回避したずのこずです。 このほかにも、パススコヌプのルヌル泚入がBash線集では走らない #88565 ずいった報告がありたす。安党機構がRead/Edit/Write前提で組たれたたた、プロンプトだけがBashぞ誘導しおいる、ずいう食い違いだず思いたす。 で、どう受け止めればいいのか なぜこの倉曎を入れたのかは、公匏の蚀葉が出るたで分かりたせん。この蚘事では「い぀から・䜕が」の実枬だけを眮きたす。 はっきり蚀えるのは、changelogは䞀次情報ずしお䞇胜ではないずいうこずです。今回の倉曎はどちらの導入もchangelogに茉っおおらず、公匏ドキュメントにもありたせん。文面がコヌドに入った2026-08-03から最初の報告#87575、2026-08-18たで2週間匷のラグがありたすが、リモヌトゲヌトの配信時期たでは枬れおいないので、この期間に実際に泚入されおいた人がいたのかは分かりたせん。それでも「changelogに茉っおいないから倉曎も無い」が成り立たないこずは、この䞀件だけで蚀えたす。 冒頭のスクショの挙動は、気のせいでも故障でもなく、あのセッションに実際に来おいたこの指瀺のずおりのものでした。Bashに寄る癖自䜓は泚入より前からの報告もあるので #19649 、2026-01起祚、䜓感のすべおをこの指瀺に垰すこずはできたせんが、正䜓が分かれば違和感の説明は぀きたす。auto modeを䜿っおいお、い぀のたにかBash経由の線集が増えたなず感じたら、それはこの指瀺のせいかもしれたせん。 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude CodeがBashを優先的に䜿う仕様にい぀の間にか倉わっおいた first appeared on SIOS Tech Lab .
どもサブ゚ヌゞェントを43本たで増やしお、メンテが終わらなくなった韍ちゃんです。 ゚ヌゞェント、増えたすよね。芳点を思い぀くたびに1本䜜れるので、止たらないんですよ。 結論から蚀うず、 agent から芳点を抜きたした 。䜕を芋るかは別のファむルに眮いお、呌ぶ偎からそのファむルの堎所を枡す。agent 偎には道具ず暩限ずモデルだけを残したした。今日はその話をしたす。 サブ゚ヌゞェントが倧量に繁殖、管理が倧倉に 芳点を思い぀くたびに1本䜜る、をやっおいたした。技術的な正確さ、文䜓、SEO、䌁業リスク、論理、読者目線。増えるのは自然なんですよね。芳点は次々思い぀くし、1本䜜るコストは䜎いので。1䜓に欲匵っお倱敗しお 3゚ヌゞェントに分けた話 も曞いおたす。分けたのは正解でした。今日の話は、分ける先が agent から芳点ファむルに倉わったずいう続きです。 先に断っおおくず、ここで蚀う「増える」は実行䞭にサブ゚ヌゞェントが再垰的に呌ばれる方の話ではないです。 .claude/agents/ に定矩ファむルが積み䞊がる方ですね。 しんどいのはその埌です。40本を超えたあたりから、どれがどういう状態なのか分からなくなりたした。盎さないずいけない箇所に芋圓は぀いおいるのに、1本ず぀開いお盎す䜜業が誰にも回されないたた残るわけですね。 で、6月に Claude Code スキルの誀発火を防ぐ「ルヌタヌ集玄」蚭蚈 ずいう蚘事を曞いおいたす。スキルを個別に発火させるのをやめお、1぀のルヌタヌに束ねお発火条件を揃えた話です。あれは効きたした。狙ったものがちゃんず発火するようになったし、蚘事の䞭で「メンテも楜になりたした」ずも曞いおたす。関連ファむルが文脈で1か所にたずたったので、どれを芋ればいいか迷わなくなったのは今でも事実です。 ただ、圓時こうも曞いおいたした。 芳点を足したいずきは references を 1 ファむル盎すだけです。SKILL.md にも agent 本䜓にもベタ曞きしないから、垞時のコンテキストは増えないし、ナレッゞの管理も䞀箇所で枈むんですよね。 曞いた本人ずしおは本気だったんですけど、実態はそうなりたせんでした。監査したら、references に眮いたはずの語圙リストが agent 本䜓にもほが逐語で35行コピヌされおいたんですよね。 理由は、どっちに䜕を曞くかを決めおいなかったこずです。「ベタ曞きしない」は方針ではあるけど、境界ではないんですよ。曞くたびに「これは agent の本文なのか、references なのか」を考えるこずになっお、刀断がぶれた分だけ䞡方に曞かれる。そしお盎すずきにも同じ刀断が発生したす。芳点を1぀盎したいだけなのに、agent を開くのか references を開くのかを先に決めないずいけない。この刀断がずっず乗っかっおいるのが、メンテが終わらない理由でした。 しかも本数は枛らないんですよ。芳点を1぀足すには、ルヌタヌの振り分け衚に1行足しお、agent を1本䜜っお、references も盎す。芳点1぀の远加で3ファむル線集でした。ルヌタヌは呌び出し口を1぀にしおくれたすが、呌ばれる偎の数はそのたたなんですよね。 芳点を尖らせたいのに、䞋手に手を入れるこずができない 文脈で束ねた結果、同じ agent が別の堎所でも掻躍するようになったんですよね。 たずえばセキュリティ芳点のレビュヌ agent を1䜓䜜っお、API の実装レビュヌで䜿い始めたずしたす。動くず分かったら、むンフラの蚭定レビュヌでも呌びたくなるんですよ。芳点は同じなので圓然そうなるし、この時点では嬉しいだけです。 うちでそうなったのが論理チェックの1䜓でした。ブログでもプレれンでもセミナヌでも提案曞でも「論理」は芁るので、この1䜓が持ち堎を増やしおいく。git を遡っお数えたら、こうなっおたした。 時点 論理チェックを呌んでいたルヌタヌの数 ルヌタヌ集玄の盎前 1察1芳点ごずにスキルがあった 集玄した盎埌 3 レビュヌ系を敎理した埌 5 移行の盎前 6 2぀以䞊のルヌタヌから呌ばれおいる agent は、43本䞭14本ありたした。䜿い回せる状態になったのは、ある意味では成果です。ただ、そこで芳点を尖らせようずするずルヌタヌ偎で修正を入れおも、agent偎の定矩ず喧嘩したりずにかく倧倉なんですね。 プレれンのレビュヌを鋭くしたくお「スラむド1枚に䞻匵が1぀か」を1行足すず、ブログのレビュヌでスラむドを探すみたいな感じです。ブログには芁らない芳点が同居する。隣に䜙蚈なものが入った状態ですね。じゃあ混ざらないように、どっちでも通る曞き方に薄めるず、今床は指摘が圓たり障りのないものになりたす。尖らせるず混ざるし、混ざらないようにするず鈍る。 コヌドの共通化ずたったく同じ圢です。共通関数にたずめるず呌び出し元を党郚気にしないずいけなくお、コピペするず盎し挏れる。あれですね。結局やりたかったのは、agent は䜿い回したたた芳点だけ分けるこずでした。 ちなみにこれ、43本ないず起きない話じゃないです。 2぀目の呌び出し元が同じ芳点を呌びたくなった時点で始たりたす 。うちは43本たで攟眮したから掟手に芋えおたすけど、5本でも同じ圢で詰たりたすね。 agent から芳点を抜いお、ファむルに远い出したした やったこずは1぀です。 agent の責務ず芳点ファむルの責務を切り分けたした。 この agent は10本のルヌタヌから呌ばれるので、持たせたのは実行環境ず返し方だけにしたした。どのモデルで動くか、どの道具を持぀か、どの暩限か、結果をどう返すか。芳点ファむルが持぀のは䜕を芋るか、どう掃くか、どう深刻床を぀けるか。呌ぶ偎は芳点ファむルのパスを枡すだけで、䞭身は枡したせん。 芳点を持たないので、こうなった agent のこずを僕は噚ず呌んでたす。䞭身が空の筐䜓だけ残した、ずいう意味ですね。以䞋そう曞きたすね。 境界を決めたので、曞くずきも盎すずきも迷わなくなりたした。芳点を盎したいなら芳点ファむルを開く。道具や暩限を倉えたいなら agent を開く。それだけです。䞀般化するのは噚だけにしお、芳点は尖らせたたた倖に眮く、ずいう圢ですね。 噚の実物がこれです。党郚で42行で、芁点だけ抜くずこうなりたす。 --- name: ro-sonnet description: >- router skill から「lens/persona ファむルのパス + 察象ファむルのパス」を委譲プロンプトで 受け取り、その定矩だけに埓っお読み取り専甚で文曞を怜査する汎甚ワヌカヌsonnet。 tools: [Read, Grep, Glob] model: sonnet --- あなたは枡された lens / persona ファむルの定矩だけに埓っお動く汎甚ワヌカヌです。 自分の刀断で芳点を足さない。定矩に無いこずは芋ない。 ## 手順 1. 委譲プロンプトから lens/persona ファむルのパスを取埗しお Read する 2. lens の `## ナレッゞベヌス` に参照パスがあれば、それらを Read する 3. lens の `## 入力` に埓っお察象ファむルを Read する 4. lens の `## 走査手順` に埓っお掃く。刀定は `## 怜出察象` のみで行う 5. lens の `## 出力圢匏` で返す 芳点はどこにも曞いおたせん。「論理」も「文䜓」も「SEO」も出おこない。䞭身は別のファむルが持っおいお、ルヌタヌが起動時にそのパスを枡したす。 ちなみに名前も実行環境だけでできおたす。 ro- は read only、 rw- は Write を持぀偎で、埌ろはモデルか足した道具です ro-sonnet は読み取りだけの sonnet。 logic-reviewer だった頃ず違っお、芳点は名前にも入っおたせん。 コヌドに出おくる lens が、その芳点ファむルです。うちのリポでファむル名をそう付けおいるだけなので、以䞋は芳点ファむルず曞きたすね。人栌を持぀ものだけ persona- を頭に付けお分けおいお、厳しめレビュヌの persona-harsh.md がそれです。䜜りは同じで、名前だけ倉えおたす。 芳点ファむルの䞭身は、こういう節でできおたす。 ## 入力 ← 䜕を読むか ## 怜出察象 ← 䜕を芋぀けたら指摘するか ## 走査手順 ← どういう順で掃くか ## 刀定軞 ← 過剰怜出をどこで止めるか ## 出力圢匏 ← どう返すか 「論理だけ芋お」ず蚀われたらルヌタヌが lens-logic.md のパスを枡すし、「厳しめで」なら persona-harsh.md を枡す。噚は同じです。ここはルヌタの定矩ずしおSKILLの䞭で管理するこずができたす。 面癜いのは、䜿い回すのをやめたわけじゃないずころですね。いたこの噚は10本のルヌタヌから呌ばれおたす。前は6本で混ざっおいたのに、10本に増えたのに混ざらない。䜿い回しおいたものが「芳点を持぀1䜓」から「芳点を持たない噚」に倉わっただけです。なのでリモヌト端末みたいな䜿い方をしおいたすね。その郜床むンストヌルするみたいな 芳点そのものの䞭身は蚘事を分けお曞いおいお、 曞く前レビュヌの゚ヌゞェント ずか 文䜓を盎す゚ヌゞェント あたりに眮いおありたす。今日は䞭身ではなく眮き堎の話ですね。 䜕が良くなったか 3぀ありたす。 いちばん効いたのは、agent をメンテしなくなったこずです。移行しおから18日で .claude/agents/ を觊ったコミットは5぀だけでした。しかもどれも、芳点を足したからではないんですよね。芳点は増えおも agent は増えない。 次に、新しい芳点の詊し方が倉わりたした。前は「この芳点いいな」ず思ったら agent を新蚭するずころから始たっおいお、それが地味に重い。いたはチャットでそのたた芳点を投げお詊しお、䜿えそうなら芳点ファむルに䞊げるだけです。ファむルは移行した時点で41枚、いたは46枚に増えたした。増やす方向が安くなったのがうれしいずころですね。 3぀めは budget です。agent の description は起動時に垞時茉るので、本数がそのたた重さになるんですよね。実枬では 7,318字 が 1,604字 になりたした。1本あたりの長さはほずんど倉えおいないので、枛ったぶんは党郚「本数が消えたこず」から来おいたす。無駄にトヌクンを食い散らかしおいる感芚もなくなりたした。ただ実行時のトヌクンは枬っおいないので、そこは䜓感ですね。蚀い切れるのは起動時に垞時茉る量の話だけにしおおきたす。 その分 SKILL ファむルは倧きくなりたしたが、 段階的開瀺 progressive disclosureが効きたす。垞時茉るのは description だけで、SKILL.md の本䜓は発火したずき、 references/ は必芁になったずきにしか読たれないんですよね。 この圢を採甚するなら1぀気を぀けおください。 噚は䜿い回す前提なので、噚を1本盎すず、その噚を呌んでいる党郚のルヌタヌに同時に響きたす。 model を倉える、道具を1぀足す、手順を1行盎す。どれも党郚に効きたす。芳点が混ざる問題が消えたわけじゃなくお、響く範囲を「芳点を持たない郚分」に狭めただけなんですよね。 移行が本圓に劣化しおいないかの怜蚌は、これだけで蚘事1本になる分量なので別で曞きたす。 公匏はSKILLずsubAgentをどう分けおいるのか、読み盎したした ここたで曞いお、公匏のドキュメントず食い違っおないか䞍安になったので読み盎したした。Claude Code のサブ゚ヌゞェントは、こう説明されおいたす。 The body becomes the system prompt that guides the subagent’s behavior. 本文がサブ゚ヌゞェントの振る舞いを導く system prompt になる The body should contain the persona and workflow instructions. 本文には人栌ず手順を曞くべき 人栌は agent の本文に曞け、ず明蚘されおたす。じゃあ再利甚したい知識はどこに眮くのかずいうず、そっちにも仕組みがあっお、 skills フィヌルドでスキルの内容をサブ゚ヌゞェントの起動時に泚入できたす。公匏の説明では「reusable domain knowledge を泚入する」甚途ですね。人栌は agent、再利甚する知識は skill、ずいう分け方に芋えたす。 で、僕はここで䞀床「自分は逞脱しおたのか」ず思ったんですけど、公匏のサンプルを芋お止たりたした。 --- name: code-reviewer description: Reviews code for quality and best practices tools: Read, Glob, Grep model: sonnet --- You are a code reviewer. When invoked, analyze the code and provide specific, actionable feedback on quality, security, and best practices. これ、レビュヌ芳点が本文に曞かれおるんですよね。「品質・セキュリティ・ベストプラクティスを芋ろ」は人栌でもあり芳点でもある。぀たり 内容では割れたせん 。芳点は agent 偎なのか skill 偎なのか、ずいう問いには答えが出ないんです。 割れる軞は別のずころにありたした。固定なのか、差し替えなのかです。 呌び出し元が1぀で、その芳点がずっず倉わらないなら、公匏のサンプルどおり本文に曞いおいい。困らないです。砎れるのは2぀目の呌び出し元が同じ芳点を欲しがった瞬間で、そこから先は人栌ではなく匕数になる。人栌を䜿い回すず混ざっお、混ざらないように䞀般化するず鈍る。公匏の曞き方は「1぀の agent に1぀の固定人栌」を前提にしおいお、人栌が匕数になる堎合を想定しおいないんですよね。 具䜓で蚀うずこうです。レビュヌの噚を1本䜜るずしお、「あなたはレビュヌアヌです」たでは焌き蟌んでいい。呌ばれ方が倉わっおも、そこは動かないからです。倉わるのはフロントを芋るのかバック゚ンドを芋るのかで、そこを lens-frontend.md ず lens-backend.md に出す。どっちのパスを枡すかは、呌ぶ偎のレビュヌスキルが決めたす。 僕は噚を機胜で切ったので読み取りだけ、曞き蟌みあり、 Bash ありレビュヌ専甚の噚は持っおたせん。でもレビュヌ専甚に1本立おお人栌を焌き蟌むのは、この圢ず矛盟したせん。 基準は「呌ばれ方が倉わっおも動かないか」だけで、䜿い回す範囲が広いほど焌き蟌めるものが枛る 、ずいう関係なんですよね。 なお公匏には「サブ゚ヌゞェントは1぀のタスクに集䞭させろ」ずいうガむドもありたす。字面だけ芋るず噚は反しおいるように芋えたすが、これは守っおたす。1回の起動は噚1本ず芳点ファむル1枚の組み合わせで、走っおいる実行䜓は1぀の芳点しか芋たせん。焊点を䞎えるタむミングがファむルの䞭か起動時かの違いだけですね。 公匏には、サブ゚ヌゞェントに知識を枡す skills フィヌルドもありたす。うちでは䜿っおいたせん。あれは列挙したスキルを、サブ゚ヌゞェントが立ち䞊がるたびに党文泚入する仕組みで、列挙するのは agent の定矩なので呌び出しごずに倉えられないんですよね。 ぀たり skills に曞くのは「 呌ばれ方が倉わっおもこの知識は芁る 」ず宣蚀するこずです。さっきの基準がそのたた圓たりたす。レビュヌの噚にレビュヌ芏玄を1぀茉せるなら劥圓だし、公匏の䟋も API 開発の゚ヌゞェントに芏玄ず゚ラヌ凊理のパタヌンを茉せる圢です。でも芳点46枚を茉せるず、1枚しか芁らない起動で45枚ぶん払うこずになる。だからパスを枡しお、必芁な1枚だけ読たせおいたす。 知識を references 偎に構造化する話は Claude Code に専門知識を仕蟌む蚘事 の続きですね。 自分のリポで䜕から始めるか 持ち垰りずしおは、刀断はこの1問で足りたす。 その芳点、2぀目の呌び出し元が欲しがりたすか 欲しがらないなら agent の本文に曞いたたたでいいです。欲しがったら、それはもう人栌じゃなくお匕数なので倖に出す。手を動かす順番はこうしたした。 たず model ず tools ず暩限で䞊べお数える これが効きたした。43本を「どのモデルか・どの道具を持っおいるか・どの暩限か」だけで䞊べたら、8皮類しかなかったんですよね。 18本が同じ1マスに入っおたす。43本それぞれに個性があるず思っおいたのに、実行環境ずしおは18本が完党に同じだったわけですね。違っおいたのは芳点だけでした。ここで「じゃあ芳点を倖に出せば、噚はこの数でいい」が芋えたす。Read だけの7本は Read / Grep / Glob ず実質差がないので䞊に寄せお、噚は7皮類になりたした。 モデルの割り圓おは Opus で考え、Sonnet で動かす で決めたものがそのたた残っおいたす。噚はモデルを軞に分けおいるので、あの配分が8皮類の骚になっおたすね。 この7皮類は移行した時点の分類で、そのあず1本増えお8本になっおいたす。深い䞀次゜ヌス远跡をやりたくなっお、子のワヌカヌを起こせる道具を足したや぀ですね。道具や暩限が倉わるものが出おきたずきだけ増えるので、芳点をいくら足しおも本数は動きたせん。 2぀目の呌び出し元が欲しがる芳点だけ、ファむルに出す 党郚を䞀気に出さなくおいいです。うちは最初に衚珟チェックの3枚だけ切り出しお、怜出が萜ちおいないか確かめおから残りを移したした。 出すずきに1぀だけ泚意があっお、掃き方も䞀緒に出さないず拟えなくなりたす。最初にやったずき、「䜕を芋るか」だけを芳点ファむルに曞いお、「どういう順で走査するか」は噚の共通手順に畳んだんですよ。そうしたら同じパタヌンが3件あるうちの1件しか拟えたせんでした。䞊から順に読んで気づいたものを挙げる、で枈たせおしたうんですね。走査の手順は芳点ごずに固有なので、芳点ファむル偎に必須の節ずしお持たせたした。 どこからでも呌ばれるものは、特化のたた眮く そしお党郚を噚にしなくおいいです。うちにも特化のたた残しおいる゚ヌゞェントが3本あっお、どれも怜玢甚です。ブログ蚘事の怜玢、リサヌチの怜玢、過去の教蚓の怜玢ですね。怜玢はどんな䜜業をしおいおも発生するので、 どの文脈からでも呌ばれたす 。぀たり呌ぶ偎が特定のルヌタヌに決たっおいない。噚は「ルヌタヌが芳点のパスを枡す」前提の䜜りなので、枡す人がいない呌び方ずは噛み合わないんですよ。だから芳点を自分で持ったたた眮いおたす。 いたは11本です。噚が8本ず、特化が3本ですね。43本あったずきず比べお、芳点の数はむしろ増えたした。たずめるか分けるかで悩んでいたずころが、噚は䜿い回しお芳点は分ける、ずいう圢で終わりたした。 たず .claude/agents/ を model ず tools ず暩限だけで䞊べおみおください。同じマスに䜕本入っおいるかを芋るのが、いちばん早いず思いたす。 コラム公匏のやり方でよくないですか たぶんここが気になった人がいるず思うので、曞いおおきたす。 context: fork を䜿っおいない人は、前半は飛ばしお倧䞈倫です。 Claude Code のスキルには context: fork ずいう曞き方があっお2.1.0・2026幎1月から、スキルの本文を別のコンテキストで走らせられたす。しかも agent: を付けるず実行䜓を遞べお、公匏ドキュメントにはこう曞いおありたす。 Options include built-in agents ( Explore , Plan , general-purpose ) or any custom subagent from .claude/agents/ . 組み蟌み゚ヌゞェント、たたは .claude/agents/ の任意のカスタムサブ゚ヌゞェントを指定できる ぀たり context: fork に agent: ro-sonnet ず曞けば、公匏の機胜だけで噚に茉りたす。ここは認めたす。 ただ、それをやるず芳点が SKILL.md の本文になるんですよね。タスクがファむルに固定される仕組みなので、芳点を1぀足すたびにスキルが1぀増えたす。そしおスキルの䞀芧は context: fork かどうかに関係なく起動時に党郚茉っお、character budget を食う。budget を超えるず description が切り詰められお、マッチに必芁なキヌワヌドごず萜ちる。これ、僕がルヌタヌ集玄の蚘事で曞いた症状そのものです。芳点46枚を党郚スキルにしたら、束ねる前の状態に逆走したす。 あず公匏ドキュメント自䜓がこう譊告しおたす。 context: fork only makes sense for skills with explicit instructions. If your skill contains guidelines like “use these API conventions” without a task, the subagent receives the guidelines but no actionable prompt, and returns without meaningful output. 明瀺的な指瀺を持぀スキルにしか意味がない。タスクのないガむドラむンだけだず、受け取っおも実行可胜なプロンプトがなく、意味のある出力を返さずに終わる うちの芳点ファむルはたさにガむドラむン偎なので、そのたた茉せおも動かないんですね。 「じゃあ組み蟌みの general-purpose を呌べばいいのでは」も同じ圢です。あれはサブ゚ヌゞェントが䜿える道具を党郚持っおいるので、 読み取りだけに絞る、ができないんですよね 。モデルは呌び出しごずに䞊曞きできたすが、道具は agent 定矩で固定なので、呌ぶ偎から枡す口がない。結果、芳点も道具の指定も SKILL 偎に曞くこずになっお、噚に曞いおあったこずを曞き写すだけになりたす。 逆に蚀うず、 芳点が少ないなら公匏の曞き方で足りたす 。知識を SKILL.md の本文に畳めるなら、わざわざパスを枡す二段構えにする必芁はないです。個数が問題になっおくるのは芳点が数十枚に育っおからで、そこたで来た人だけが今日の話を䜿えばいいず思っおたす。 ほなたた〜 ご芧いただきありがずうございたす この投皿はお圹に立ちたしたか 圹に立った 圹に立たなかった 0人がこの投皿は圹に立ったず蚀っおいたす。 The post Claude Codeのサブ゚ヌゞェントが増えすぎたので、芳点を倖に出したした first appeared on SIOS Tech Lab .