print("url:", url)
parts=url.split("/")
print("parts[3],parts[4],parts[6] =>", parts[3], parts[4], parts[6])
PY

cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
import requests

owner, repo, issue_number = "arii", "hrm", "1600"
api = f"https://api.github.com/repos/{owner}/{repo}/issues/{issue_number}"

r = requests.get(api, headers=SENDER.headers)
print("GET", api)
print("status:", r.status_code)
print("x-ratelimit-remaining:", r.headers.get("x-ratelimit-remaining"))
print("body:", r.text[:400])
PY

set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
BACKUP="${FILE}.bak.postfix.$(date +%s)"
echo "== 0) Backup =="
sudo /usr/bin/cp "$FILE" "$BACKUP"
echo "Backup: $BACKUP"
echo
echo "== 1) Patch post_issue_comment() to use correct GitHub REST endpoint =="
sudo /usr/bin/python3 - <<'PY'
from pathlib import Path
import re

p = Path("/Eden/CORE/eden_github_sender.py")
s = p.read_text(encoding="utf-8", errors="ignore")

# We replace the whole post_issue_comment method body if it exists.
m = re.search(r"^(\s*)def\s+post_issue_comment\s*\(\s*self\s*,\s*owner\s*,\s*repo\s*,\s*issue_number\s*,\s*message\s*\)\s*:\s*$", s, re.M)
if not m:
    raise SystemExit("❌ Could not find def post_issue_comment(self, owner, repo, issue_number, message)")

indent = m.group(1)
start = m.start()

# Find end of this def by locating next top-level def with same indentation (or EOF)
pattern_next = re.compile(rf"^{re.escape(indent)}def\s+\w+\s*\(", re.M)
mn = pattern_next.search(s, m.end())
end = mn.start() if mn else len(s)

new_def = f"""{indent}def post_issue_comment(self, owner, repo, issue_number, message):
{indent}    # Correct endpoint:
{indent}    # POST /repos/{{owner}}/{{repo}}/issues/{{issue_number}}/comments
{indent}    api_url = f"https://api.github.com/repos/{{owner}}/{{repo}}/issues/{{issue_number}}/comments"
{indent}    payload = {{"body": message}}
{indent}    r = requests.post(api_url, headers=self.headers, json=payload, timeout=20)
{indent}    try:
{indent}        data = r.json()
{indent}    except Exception:
{indent}        data = {{"raw": r.text}}
{indent}    if r.status_code in (200, 201):
{indent}        return True, data
{indent}    return False, data

"""

s2 = s[:start] + new_def + s[end:]
p.write_text(s2, encoding="utf-8")
print("✅ Patched post_issue_comment() endpoint + error handling")
PY

echo
echo "== 2) Syntax check =="
sudo /usr/bin/python3 -m py_compile /Eden/CORE/eden_github_sender.py && echo "✅ syntax ok"
echo
echo "== 3) Live test: post a tiny comment to the known-good issue =="
cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
ok, data = SENDER.post_issue_comment("arii","hrm","1600","Eden debug: posting pipeline test. If you see this, sender endpoint is fixed.")
print("ok:", ok)
print("data:", str(data)[:500])
PY

set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
BACKUP="${FILE}.bak.blocked.$(date +%s)"
echo "== 0) Backup =="
sudo /usr/bin/cp "$FILE" "$BACKUP"
echo "Backup: $BACKUP"
echo
echo "== 1) Patch send_issue_comment to mark blocked vs failed correctly =="
sudo /usr/bin/python3 - <<'PY'
from pathlib import Path
import re

p=Path("/Eden/CORE/eden_github_sender.py")
s=p.read_text(encoding="utf-8", errors="ignore").splitlines()

# Find the line where success is handled: "if success:" block that updates outreach_queue to sent.
# We'll also add failure handling to set status='blocked' when GitHub returns collaborator restriction.
out=[]
i=0
patched=False

while i < len(s):
    line=s[i]
    out.append(line)

    # Insert helper classifier once (idempotent)
    if (not any("def _classify_post_error" in x for x in s)) and re.match(r"^\s*class\s+GitHubSender\s*:", line):
        out.append("")
        out.append("    def _classify_post_error(self, response_dict):")
        out.append("        try:")
        out.append("            msg = (response_dict or {}).get('message','') or ''")
        out.append("            errs = (response_dict or {}).get('errors',[]) or []")
        out.append("            blob = (msg + ' ' + str(errs)).lower()")
        out.append("            if 'restricted to collaborators only' in blob or 'interactions on this repository have been restricted' in blob:")
        out.append("                return 'blocked'")
        out.append("        except Exception:")
        out.append("            pass")
        out.append("        return 'failed'")
        out.append("")
        continue

    i += 1

# Now patch the send path: locate the UPDATE to status='sent'
text="\n".join(out)

# Add: on failure, update outreach_queue status to blocked/failed with sent_at timestamp and no bump.
# We'll patch by injecting after the success block where it currently returns error.
# We look for the exact line you previously showed:
# conn.execute("UPDATE outreach_queue SET status='sent', sent_at=? WHERE id=?",
pat = r"conn\.execute\(\"UPDATE outreach_queue SET status='sent', sent_at=\? WHERE id=\?\","
m = re.search(pat, text)
if not m:
    raise SystemExit("❌ Could not find the status='sent' update line to anchor patch.")

# Also find the "else:" that returns GitHub API failed inside send_issue_comment
# We'll do a minimal, safe patch: replace the `else:` branch that currently just closes and returns error,
# with an update that sets blocked/failed.
lines=text.splitlines()

new=[]
i=0
while i < len(lines):
    new.append(lines[i])

    # Detect the failure return in the send_issue_comment flow
    if re.search(r"return\s+\{'error':\s*'GitHub API failed'", lines[i]):
        # Backtrack a bit to find indentation of this return
        indent = re.match(r"^(\s*)", lines[i]).group(1)
        # Insert nothing here; we'll patch earlier where `success` is False.
    i += 1

# Patch using a more direct approach: replace the `else:` block after `if success:` in send_issue_comment.
patched_lines=[]
i=0
while i < len(lines):
    line=lines[i]
    patched_lines.append(line)

    # Find "if success:" line
    if re.match(r"^\s*if\s+success\s*:\s*$", line):
        # Walk forward until we find the matching "else:" at same indentation
        if_indent = re.match(r"^(\s*)", line).group(1)
        j=i+1
        else_idx=None
        while j < len(lines):
            if re.match(rf"^{re.escape(if_indent)}else\s*:\s*$", lines[j]):
                else_idx=j
                break
            j+=1
        if else_idx is None:
            i+=1
            continue

        # Copy lines between i+1 and else_idx-1 as-is (already copied progressively),
        # but when we hit else_idx, replace the else block content.
        # We need to skip original else block body. We'll detect its indentation and skip until it dedents.
        # First, append the else line itself (replace later)
        # But we already appended current line; we haven't appended else line yet.
        # So let normal loop continue until else_idx, then handle there.
        pass

    # When at else line that matches the if success indentation, replace body
    if re.match(r"^\s*else\s*:\s*$", line):
        # Need to ensure this else is the one inside send_issue_comment; we look ahead for "GitHub API failed" return
        lookahead="\n".join(lines[i:i+20])
        if "GitHub API failed" in lookahead and "conn.close()" in lookahead:
            indent = re.match(r"^(\s*)", line).group(1)
            body_indent = indent + "    "
            # Skip existing else body
            j=i+1
            while j < len(lines):
                if lines[j].startswith(indent) and not lines[j].startswith(body_indent) and lines[j].strip()!="":
                    break
                # stop at a return to avoid eating too much if file is weird
                if j>i+1 and re.match(rf"^{re.escape(indent)}(elif|else|def|class)\b", lines[j]):
                    break
                j+=1

            # Replace with our safe block
            patched_lines.append(f"{body_indent}status = self._classify_post_error(response)")
            patched_lines.append(f"{body_indent}conn.execute(\"UPDATE outreach_queue SET status=?, sent_at=CURRENT_TIMESTAMP WHERE id=?\", (status, queue_id))")
            patched_lines.append(f"{body_indent}conn.commit()")
            patched_lines.append(f"{body_indent}conn.close()")
            patched_lines.append(f"{body_indent}return {{'error': status, 'response': response}}")
            patched=True
            i=j
            continue

    i+=1

if not patched:
    raise SystemExit("❌ Did not patch else-branch in send_issue_comment (pattern mismatch).")

p.write_text("\n".join(patched_lines)+"\n", encoding="utf-8")
print("✅ Patched: blocked vs failed classification + DB status update on failure")
PY

set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
TS="$(date +%s)"
BACKUP="${FILE}.bak.blockedfix.${TS}"
LOG="/tmp/eden_blockedfix.${TS}.log"
echo "== 0) Backup ==" | tee "$LOG"
sudo /usr/bin/cp "$FILE" "$BACKUP"
echo "Backup: $BACKUP" | tee -a "$LOG"
echo | tee -a "$LOG"
echo "== 1) Write patch script ==" | tee -a "$LOG"
cat >/tmp/patch_blocked.py <<'PY'
from pathlib import Path
import re

FILE = "/Eden/CORE/eden_github_sender.py"
p = Path(FILE)
lines = p.read_text(encoding="utf-8", errors="ignore").splitlines()

def has_helper(ls):
    return any("def _classify_post_error" in x for x in ls)

def insert_helper(ls):
    out=[]
    inserted=False
    for i, line in enumerate(ls):
        out.append(line)
        if (not inserted) and re.match(r"^\s*class\s+GitHubSender\s*:\s*$", line):
            out.append("")
            out.append("    def _classify_post_error(self, response_dict):")
            out.append("        try:")
            out.append("            msg = (response_dict or {}).get('message','') or ''")
            out.append("            errs = (response_dict or {}).get('errors',[]) or []")
            out.append("            blob = (msg + ' ' + str(errs)).lower()")
            out.append("            if ('restricted to collaborators only' in blob) or ('interactions on this repository have been restricted' in blob):")
            out.append("                return 'blocked'")
            out.append("        except Exception:")
            out.append("            pass")
            out.append("        return 'failed'")
            out.append("")
            inserted=True
    return out, inserted

def patch_failure_else(ls):
    # Patch the else: branch in send_issue_comment that currently returns GitHub API failed without DB update.
    out=[]
    i=0
    patched=False

    while i < len(ls):
        line = ls[i]
        out.append(line)

        # Identify the else: that belongs to "if success:" after post_issue_comment() call,
        # by looking ahead for the "GitHub API failed" return.
        if re.match(r"^\s*else\s*:\s*$", line):
            indent = re.match(r"^(\s*)", line).group(1)
            look = "\n".join(ls[i:i+25])
            if ("GitHub API failed" in look) and ("response" in look):
                body_indent = indent + "    "
                # Skip old else body until it dedents back to indent level with code (or EOF)
                j=i+1
                while j < len(ls):
                    if ls[j].startswith(indent) and (not ls[j].startswith(body_indent)) and ls[j].strip() != "":
                        break
                    j += 1

                # Replace else body with DB update (blocked/failed) and no daily_limits bump
                out.append(f"{body_indent}status = self._classify_post_error(response)")
                out.append(f"{body_indent}try:")
                out.append(f"{body_indent}    conn.execute(\"UPDATE outreach_queue SET status=?, sent_at=CURRENT_TIMESTAMP WHERE id=?\", (status, queue_id))")
                out.append(f"{body_indent}    conn.commit()")
                out.append(f"{body_indent}except Exception:")
                out.append(f"{body_indent}    pass")
                out.append(f"{body_indent}conn.close()")
                out.append(f"{body_indent}return {{'error': status, 'response': response}}")

                patched=True
                i=j
                continue

        i += 1

    return out, patched

# 1) ensure helper exists
if not has_helper(lines):
    lines, inserted = insert_helper(lines)
else:
    inserted = False

# 2) patch else branch
lines2, patched = patch_failure_else(lines)

if not patched:
    raise SystemExit("❌ Could not find/patch the failure else-branch (pattern mismatch).")

p.write_text("\n".join(lines2) + "\n", encoding="utf-8")
print("✅ Applied blocked/failed classification patch")
print("   helper_inserted:", inserted)
print("   else_branch_patched:", patched)
PY

echo | tee -a "$LOG"
echo "== 2) Run patch script ==" | tee -a "$LOG"
sudo /usr/bin/python3 /tmp/patch_blocked.py 2>&1 | tee -a "$LOG"
echo | tee -a "$LOG"
echo "== 3) Syntax check ==" | tee -a "$LOG"
sudo /usr/bin/python3 -m py_compile /Eden/CORE/eden_github_sender.py 2>&1 | tee -a "$LOG"
echo "✅ syntax ok" | tee -a "$LOG"
echo | tee -a "$LOG"
echo "== 4) Prove patch exists ==" | tee -a "$LOG"
grep -n "def _classify_post_error" -n /Eden/CORE/eden_github_sender.py | head -n 5 | tee -a "$LOG"
grep -n "return {'error': status" -n /Eden/CORE/eden_github_sender.py | head -n 5 | tee -a "$LOG"
echo | tee -a "$LOG"
echo "DONE. Log: $LOG"
set -euo pipefail
DB="/Eden/DATA/sales.db"
echo "== 0) Snapshot daily_limits UTC BEFORE =="
sudo /usr/bin/sqlite3 "$DB" "SELECT date, outreach_count, last_outreach_at FROM daily_limits WHERE date=date('now');"
echo
echo "== 1) Call sender against restricted repo (should be blocked) =="
cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
res = SENDER.send_comment("https://github.com/arii/hrm/issues/1600", "Eden debug: blocked classification test.", "TEST_QUEUE_ID", None)
print(res)
PY

echo
echo "== 2) Snapshot daily_limits UTC AFTER (should be unchanged) =="
sudo /usr/bin/sqlite3 "$DB" "SELECT date, outreach_count, last_outreach_at FROM daily_limits WHERE date=date('now');"
set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
echo "== 1) Show the exact post_issue_comment() implementation (authoritative) =="
sudo /usr/bin/python3 - <<'PY'
import re
p="/Eden/CORE/eden_github_sender.py"
lines=open(p,"r",encoding="utf-8",errors="ignore").read().splitlines()
start=None
for i,l in enumerate(lines):
    if re.match(r"^\s*def\s+post_issue_comment\s*\(", l):
        start=i
        break
if start is None:
    raise SystemExit("❌ post_issue_comment not found")
end=len(lines)
for j in range(start+1,len(lines)):
    if re.match(r"^\s*def\s+\w+\s*\(", lines[j]) and (len(lines[j]) - len(lines[j].lstrip())) <= (len(lines[start]) - len(lines[start].lstrip())):
        end=j
        break
for k in range(start, min(end, start+140)):
    print(f"{k+1:4d}: {lines[k]}")
PY

echo
echo "== 2) Live API test: POST to the issue's OWN comments_url (bypasses our code) =="
cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
import requests

owner, repo, num = "arii", "hrm", "1600"
issue_api = f"https://api.github.com/repos/{owner}/{repo}/issues/{num}"
r = requests.get(issue_api, headers=SENDER.headers)
print("GET issue:", r.status_code)
j = r.json()
comments_url = j.get("comments_url")
print("comments_url:", comments_url)

payload = {"body":"Eden debug: comment endpoint verification (if you see this, POST works)."}
rp = requests.post(comments_url, headers=SENDER.headers, json=payload)
print("POST comments_url status:", rp.status_code)
print("POST body head:", rp.text[:400])
PY

set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
TS="$(date +%s)"
BACKUP="${FILE}.bak.classifyfix.${TS}"
echo "== 0) Backup =="
sudo /usr/bin/cp "$FILE" "$BACKUP"
echo "Backup: $BACKUP"
echo
echo "== 1) Patch: strengthen blocked classification + include http status in post_issue_comment =="
sudo /usr/bin/python3 - <<'PY'
from pathlib import Path
import re

p = Path("/Eden/CORE/eden_github_sender.py")
s = p.read_text(encoding="utf-8", errors="ignore")

# 1) Ensure post_issue_comment injects status_code into returned dict (so classifier can rely on it)
# Add right after we decode JSON into `data` (or raw) and before the status_code check.
if "data['http_status']" not in s:
    s = re.sub(
        r"(data\s*=\s*r\.json\(\)\s*)",
        r"\1\n            data['http_status'] = r.status_code\n",
        s,
        flags=re.M
    )
    s = re.sub(
        r"(data\s*=\s*\{\"raw\":\s*r\.text\}\s*)",
        r"\1\n            data['http_status'] = r.status_code\n",
        s,
        flags=re.M
    )

# 2) Replace/insert a robust classifier method inside the class.
# We will overwrite the body of def _classify_post_error(self, response_dict): if it exists.
pat = r"(?ms)^(\s*)def\s+_classify_post_error\s*\(\s*self\s*,\s*response_dict\s*\)\s*:\s*\n.*?(?=^\s*def\s|\Z)"
m = re.search(pat, s)
new = (
"    def _classify_post_error(self, response_dict):\n"
"        try:\n"
"            msg = str(response_dict.get('message',''))\n"
"            status = str(response_dict.get('status', response_dict.get('http_status','')))\n"
"            doc = str(response_dict.get('documentation_url',''))\n"
"            blob = (msg + ' ' + doc + ' ' + str(response_dict)).lower()\n"
"        except Exception:\n"
"            return 'failed'\n"
"\n"
"        # Hard blocks (non-retriable): repo restrictions, permissions, private repos, org policies.\n"
"        if status in ('401','403','404','410','422'):\n"
"            # GitHub uses 422 for 'restricted to collaborators' and 404/403 for permission blocks.\n"
"            if 'restricted to collaborators' in blob:\n"
"                return 'blocked'\n"
"            if status in ('401','403','404','410'):\n"
"                return 'blocked'\n"
"            if status == '422':\n"
"                return 'blocked'\n"
"\n"
"        # Rate limiting / temp failures (retriable)\n"
"        if status in ('429','500','502','503','504'):\n"
"            return 'failed'\n"
"        if 'rate limit' in blob or 'abuse detection' in blob:\n"
"            return 'failed'\n"
"\n"
"        return 'failed'\n"
)

if m:
    s = s[:m.start()] + new + s[m.end():]
else:
    # Insert right after class GitHubSender: line
    s = re.sub(r"(?m)^(class\s+GitHubSender\s*:\s*)$", r"\1\n\n" + new, s, count=1)

p.write_text(s, encoding="utf-8")
print("✅ Patched classifier + ensured http_status is present in responses")
PY

echo
echo "== 2) Syntax check =="
sudo /usr/bin/python3 -m py_compile /Eden/CORE/eden_github_sender.py && echo "✅ syntax ok"
echo
echo "== 3) Re-test restricted repo (should classify as blocked AND not bump limits) =="
DB="/Eden/DATA/sales.db"
echo "-- daily_limits BEFORE --"
sudo /usr/bin/sqlite3 "$DB" "SELECT date, outreach_count, last_outreach_at FROM daily_limits WHERE date=date('now');"
cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
ok, data = SENDER.send_comment("https://github.com/arii/hrm/issues/1600", "Eden debug: blocked classification test v2.", "TEST_QUEUE_ID", None)
print("ok:", ok)
print("data:", data)
PY

set -euo pipefail
DB="/Eden/DATA/sales.db"
echo "== daily_limits BEFORE (UTC) =="
sudo /usr/bin/sqlite3 "$DB" "SELECT date, outreach_count, last_outreach_at FROM daily_limits WHERE date=date('now');"
echo
echo "== Call sender (restricted repo) =="
cd /Eden/CORE
PYTHONPATH=/Eden/CORE /usr/bin/python3 - <<'PY'
from eden_github_sender import SENDER
ok, data = SENDER.send_comment("https://github.com/arii/hrm/issues/1600", "Eden debug: classification should be blocked.", "TEST_QUEUE_ID", None)
print("ok:", ok)
print("data:", data)
PY

set -euo pipefail
FILE="/Eden/CORE/eden_github_sender.py"
BACKUP="${FILE}.bak.blocked.$(date +%s)"
echo "== 0) Backup =="
sudo /usr/bin/cp "$FILE" "$BACKUP"
echo "Backup: $BACKUP"
echo
echo "== 1) Patch send_issue_comment to mark blocked vs failed correctly =="
sudo /usr/bin/python3 - <<'PY'
from pathlib import Path
import re

p=Path("/Eden/CORE/eden_github_sender.py")
s=p.read_text(encoding="utf-8", errors="ignore").splitlines()

# Find the line where success is handled: "if success:" block that updates outreach_queue to sent.
# We'll also add failure handling to set status='blocked' when GitHub returns collaborator restriction.
out=[]
i=0
patched=False

while i < len(s):
    line=s[i]
    out.append(line)

    # Insert h[seen-bssids]
e4f9dd28-a1f4-4f3d-bf43-12f3f218d234=C0:D7:AA:3B:F7:69,C0:D7:AA:3B:F7:6A,C0:D7:AA:3B:F7:68,
