久しくエントリを書いてなかったおごちゃんです。

このところずっとエージェントシステムを書いていて集中してたからです。

ところがですねー、この何ヶ月もずっと書いていたシステムのコードがまるっと消えてしまったんですよ。

広告システムを作っている話は以前書きました。

AIによるAmazon広告運用システム

これ以後ブログも書かずにずっとコード書いてたんですよ。 どんなもので何がどうなるかは、近いうちに公開できると思います。

というエントリでも書こうかと思って、件のシステムのテストで出た大量のログがあったので、VS Codeのファイル削除で消したんです。

そしたら、何がどう悪かったのか知りませんが、プロジェクトのファイルが一瞬にして全部消えてしまいました

「Gitがあるじゃーん」という話があるんですが、私の悪いクセで構造がまだ定まっていないプロジェクトはGitに入れないんです。 一応initして初期commitはしてあるのですが、その後はまるでcommitせず。 ほぼまるっとプロジェクトごと消えて呆然…

そこはいつものクセで、「まぁ一度書いたコードだし、消えてしまってもまた書けばいいし、それはそれで見直しのチャンスだし」と気を取り直そうとしてChatGPTにグチったんです。

VS Codeが暴走して、プロジェクトフォルダ全消ししやがった...

すると、奴は律儀にも対応策を提案してくれました。

コードの復旧

Gitには入れてませんし、VS Codeのファイル削除は完全削除だし、ファイルシステムにext4を使ってるのでタイムマシンもなく(これがSeawead FSだったら…)。

まぁそんなわけで、私の知っている範囲では救出不可能な状態となりました。 何年かに一度やらかす、よくある事故です。 こちらも慣れているので、

まぁ一度書いたコードだし、消えてしまってもまた書けばいいし、それはそれで見直しのチャンスだし

と納得するのが常です。

しかし律儀なチャッピーは、

3. VS CodeのLocal Historyを確認

VS Codeには拡張や環境によって Timeline / Local History が残っている場合があります。

対象フォルダが消えていても、VS Code側の履歴が残っていることがあります。

とか教えてくれるわけです。 つまり、~/.vscode-server/data/User/Historyあたりに、ファイルの痕跡があるかも知れないという話です。

これは何のフォルダかと言えば、「VS Codeで触ったファイルの履歴」です。 何のために使うのかよくわかってないんですが、VS Codeはsaveの時にここにもファイルを書いています。 名前はそのままじゃなくて、ハッシュ化したファイル毎のフォルダがあって、その中にファイルの更新履歴のファイルが、これまたハッシュ化したファイル名で保存されています。 そして、そのファイルの属性や履歴のタイムスタンプが、entries.jsonというファイルに保存されています。

{
  "version": 1,
  "resource": "vscode-remote://ssh-remote%2Bshibuya.local/home/ogochan/git/Legion/src/core/legion.js",
  "entries": [
    {
      "id": "yRXw.js",
      "source": "undoRedo.source",
      "timestamp": 1779554937165
    }
  ]
}

こんな感じです。 これは履歴のないファイルですが、通常はentriesに履歴があります。

なので、手順としては

  • ~/.vscode-server/data/User/Historyの中を見る
  • 当該セションのフォルダを見つける
  • その下に並んでいるフォルダ群を見る
  • そのフォルダの中のentries.jsonを見る
  • この情報を元に最新のものを正しい名前で取得する

ということを行います。 こうすればVS Codeで編集したファイルは復元することができます。 通常消えて青ざめるのはコードのファイルでしょうから、これで何とかできます。

これはぼちぼち手でやってもいいんですが、いっぱいあると大変なので、チャッピーはコードを作ってくれました。

#!/usr/bin/env python3
import json
import sys
from pathlib import Path
from datetime import datetime, timezone
from urllib.parse import unquote, urlparse

HISTORY_ROOTS = [
    Path.home() / ".vscode-server/data/User/History",
    Path.home() / ".vscode-server-insiders/data/User/History",
]

needle = sys.argv[1] if len(sys.argv) > 1 else None
if not needle:
    print("usage: python3 inspect-vscode-history-file.py <path-fragment>")
    sys.exit(1)

def decode_resource(s):
    if not isinstance(s, str):
        return str(s)
    if s.startswith("file://") or s.startswith("vscode-remote://"):
        p = urlparse(s)
        return unquote(p.path)
    return unquote(s)

def ts_to_text(ts):
    if not isinstance(ts, (int, float)) or ts <= 0:
        return "no-timestamp"
    # VS Codeはmsの場合が多い
    if ts > 10_000_000_000:
        ts = ts / 1000
    try:
        return datetime.fromtimestamp(ts, timezone.utc).astimezone().isoformat()
    except Exception:
        return str(ts)

for root in HISTORY_ROOTS:
    if not root.exists():
        continue

    for entries_json in root.glob("*/entries.json"):
        try:
            data = json.loads(entries_json.read_text(encoding="utf-8"))
        except Exception:
            continue

        resource = data.get("resource") or data.get("source") or data.get("uri") or data.get("path") or data.get("fsPath")
        resource_text = decode_resource(resource)

        if needle not in resource_text and needle not in str(resource):
            continue

        print()
        print("=" * 80)
        print(f"entries:  {entries_json}")
        print(f"resource: {resource}")
        print(f"decoded:  {resource_text}")

        entries = data.get("entries") or []
        if not entries:
            print("no entries")
            continue

        rows = []
        for i, e in enumerate(entries):
            hist_id = e.get("id") or e.get("name") or e.get("source") or e.get("file") or e.get("path")
            ts = e.get("timestamp") or e.get("mtime") or e.get("ctime") or e.get("time") or 0
            hist_file = entries_json.parent / str(hist_id) if hist_id else None
            exists = hist_file.exists() if hist_file else False
            size = hist_file.stat().st_size if exists else None
            mtime = hist_file.stat().st_mtime if exists else None
            rows.append((i, ts, hist_id, exists, size, mtime, hist_file))

        # timestampが無い場合もあるので、ファイルmtimeでも見られるよう両方出す
        for i, ts, hist_id, exists, size, mtime, hist_file in rows:
            print(
                f"[{i}] "
                f"entry_ts={ts_to_text(ts)} "
                f"file_mtime={ts_to_text(mtime or 0)} "
                f"exists={exists} "
                f"size={size} "
                f"id={hist_id}"
            )
            if exists:
                print(f"    {hist_file}")

これで99%くらいは復活するので、後はぼちぼち何とかして行きます。

「99%」はどうしてかと言えば、プログラムが生成したファイルとか、履歴保存の対象にしてないファイルとかは、ここに残っていないからです。 今回の場合package.jsonが全く保存されてなくて、最初から作り直す羽目になりました。 まぁ、package.jsonはほぼ自動生成のままですし、自動とか言ってもパッケージのインストールくらいしかしてないので、どうとでもなりますし。 もちろんnode_modules/も同じことなんですが、こっちはどうとでもなります。

反省

する気はない(キリッ!

コードの構造が安定する前は、結局試行錯誤をして来たものですから、また書いてしまえば最短コースです。 初期の大規模リファクタリングのチャンスが強制的にやって来たと思えば、「それもアリだね」と納得すればいい… というのが、業界40年生としての考えです。 現実に今までやっていた作業の何割かは途中でゴミ箱(junk/というフォルダ作ってます)に捨ててますし。

とは言え、絶望感もハンパないですから、最悪こんな手もあるということで覚えておけばいいんじゃないかと思います。

「Git入れとけよー」は正論としては正しいんですが、

事故は対策の隙間で起きる

ものなので、Gitが万能薬である保証はありません。 たまたまcommitの間があったとか、push忘れてるのに.gitを消してしまったとか、そういったことが起きるかも知れませんし、さらに予測もつかないことが起きることも考えられます。

今回の事故も元々「たかがログを消す」という行為で、しかもそれは「事故を防ぐため」にshellからrmするのを避けてVS Codeの中からやった結果なので、何が仇になるかはわかりません。 rmを手で入力するのは危険ですから、なるべくしたくありませんね。 「クリーンアップ」はハイリスク行為だと思うべきで、それは注意し過ぎてし過ぎではありません。

でも、「注意する」「ツールを使う」のも完全ではありません。

今回のは原因よくわからないんですが、プロジェクトを超えて(他のプロジェクトのファイルも消された)起きたことです。 VS Codeのバグなんだろうと勝手に思ってますし、それが仮に操作ミスの一種の結果であってもガードがなかったとか間違いやすかった何かがあったとか、そんなことでしょう。 事実として「思いもしないこと、予防策の外側のこと」が起きたわけです。

「結果論」で説教垂れるのが許されるのは小学生までです。 大事なのは「納得」と「リカバリ」です。 これがあれば原因が何であれ良い結果が残ります

またこういった時は案外「ムダな複製」で命を拾うこともあるので、コスパだ合理化だと言ってムダを省き過ぎるのは命を拾う機会を捨てているかも知れない… という疑問は持って良いでしょう。 まぁ「これ何だっけ?」って邪魔になることも多いんで綺麗にはしておきたい気持ちもありますけど。

最近のエントリー

ソースコードがまるっと消えた

金曜ごはん#31 「限界突破の1ポンド超えハンバーグナイト」

金曜ごはん#30 「野蒜と焼肉」

金曜ごはん#29「肉の旨味重なるWミートグラタン」

初夏の磯遊び

金曜ごはん#28 「パワーディナー」

おいしいプリン

金曜ごはん#27 「たっぷり唐揚げ」

金曜ごはん#26 「骨付きスペアリブ」

金曜ごはん#25「久しぶりのピザ」

金曜ごはん#24「タンドリーチキンとナン」

金曜ごはん #23 「トースターで焼いた手作りパン」

金曜ごはん #22 「お惣菜と手作りミートソース」

金曜ごはん#21「ケンタッキー風鶏排」

Geminiで音楽が作れるようになったので、いくつかザリガニの曲を作ってみた

Hieronymus MCP計画

金曜ごはん#20 「ケンタッキー風フライドチキン」

AIによるAmazon広告運用システム

バックオフィスを“自分たちの手に戻す”ためのERP:Hieronymus公式サイトを公開しました🎉

金曜ごはん#19 「鶏つみれ鍋」